diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index bd633c5..0cabda9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,18 +3,6 @@ FROM ghcr.io/containercraft/devcontainer:latest -# Install additional dependencies by extending the base image -#ARG DEBIAN_FRONTEND=noninteractive -#ARG APT_PACKAGES="\ -#" -# -#RUN set -ex \ -# && apt-get update \ -# && apt-get install -y $APT_PACKAGES \ -# && apt-get clean \ -# && rm -rf /var/lib/apt/lists/* \ -# && echo - ################################################################################## # Install AWS CLI v2 @@ -41,6 +29,44 @@ RUN echo \ && echo USER vscode +################################################################################## +# Install Poetry for Python dependency management +#USER root +#ENV POETRY_HOME="/usr/local" \ +# PATH="${POETRY_HOME}/bin:${PATH}" +# +#RUN PYTHON_VERSION=$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2) \ +# && NAME="poetry" \ +# && echo "---------------------------------------------------------" \ +# && echo "INFO[${NAME}] Installing:" \ +# && echo "INFO[${NAME}] Command: ${NAME}" \ +# && echo "INFO[${NAME}] Python Version: ${PYTHON_VERSION}" \ +# && echo "---------------------------------------------------------" \ +# && curl -sSL https://install.python-poetry.org | python3 - \ +# && chmod +x /usr/local/bin/poetry \ +# && poetry config virtualenvs.in-project true \ +# && poetry --version \ +# && mkdir -p /home/vscode/.config/pypoetry \ +# && chown -R vscode:vscode /home/vscode/.config \ +# && echo +# +#USER vscode +#RUN poetry config virtualenvs.in-project true \ +# && poetry config virtualenvs.create true + +################################################################################### +# Install additional dependencies by extending the base image +#ARG DEBIAN_FRONTEND=noninteractive +#ARG APT_PACKAGES="\ +#" +# +#RUN set -ex \ +# && apt-get update \ +# && apt-get install -y $APT_PACKAGES \ +# && apt-get clean \ +# && rm -rf /var/lib/apt/lists/* \ +# && echo + ################################################################################### ## Install Google Cloud SDK # diff --git a/.envrc b/.envrc index 2e4fa14..449bb76 100644 --- a/.envrc +++ b/.envrc @@ -1,30 +1,33 @@ -source .tmpenv # 2>/dev/null || true -################################################################################## -# bash bin -export PATH=$PATH:.github/bin +#!/usr/bin/env bash -# Platform Architecture -#export ARCH=$(uname -m | awk '{ if ($$1 == "x86_64") print "amd64"; else if ($$1 == "aarch64" || $$1 == "arm64") print "arm64"; else print "unknown" }') +# Source environment variables from .env and temporary environment +source_env .env 2>/dev/null || true +source .tmpenv 2>/dev/null || true -################################################################################## -# Basic Config Variables +# Add local bin directories to PATH +PATH_add .venv/bin +PATH_add .github/bin + +# Platform Architecture Detection +export ARCH=$(uname -m | awk '{ if ($1 == "x86_64") print "amd64"; else if ($1 == "aarch64" || $1 == "arm64") print "arm64"; else print "unknown" }') + +# Basic Configuration export BROWSER=echo export KUBECONFIG=$PWD/.kube/config export TALOSCONFIG=$PWD/.talos/manifest/talosconfig -#export OMNICONFIG=.talos/omniconfig -################################################################################## -# Pulumi Environment Variables -# - https://www.pulumi.com/docs/cli/environment-variables +# Pulumi Configuration +# See: https://www.pulumi.com/docs/cli/environment-variables export PULUMI_SKIP_UPDATE_CHECK=true export PULUMI_SKIP_CONFIRMATIONS=true export PULUMI_AUTOMATION_API_SKIP_VERSION_CHECK=true -#export PULUMI_K8S_DELETE_UNREACHABLE=true -#export PULUMI_HOME=$PWD/.pulumi -################################################################################## -# Optional Pulumi Environment Variables -# Useful for CI testing -# Uncomment to use local backend instead of Pulumi Cloud +# Optional Configuration (commented out by default) +#export OMNICONFIG=.talos/omniconfig + +# Optional Pulumi Backend Configuration +# Uncomment for local backend instead of Pulumi Cloud #export PULUMI_BACKEND_URL=${PULUMI_BACKEND_URL:-file://$PWD/.pulumi} #export PULUMI_CONFIG_PASSPHRASE=${PULUMI_CONFIG_PASSPHRASE:-foobarbaz} +#export PULUMI_HOME=$PWD/.pulumi +#export PULUMI_K8S_DELETE_UNREACHABLE=true diff --git a/.github/bin/cloc b/.github/bin/cloc new file mode 100755 index 0000000..c1fbeb7 --- /dev/null +++ b/.github/bin/cloc @@ -0,0 +1,69 @@ +#!/bin/bash +set -euo pipefail + +# Script to count non-comment, non-docstring lines of Python code in Pulumi IaC +# Usage: ./cloc +# Description: Analyzes Python code in the ./pulumi directory, excluding: +# - Comments (lines starting with #) +# - Empty lines and whitespace +# - Docstrings (multi-line strings between """ or ''') +# - Inline comments (# after code) + +# Ensure we're in the root directory +cd "$(dirname "$0")/../.." || exit 1 + +# Verify pulumi directory exists +if [[ ! -d "./pulumi" ]]; then + echo "Error: ./pulumi directory not found!" + exit 1 +fi + +echo "Analyzing Python code in Pulumi Infrastructure as Code (IaC) directory..." +echo "Excluding comments, docstrings, and blank lines..." + +# Count lines across all Python files +total_lines=$(find ./pulumi -name "*.py" -type f -exec awk ' + BEGIN { + in_docstring = 0 + count = 0 + } + { + # Store original line for inline comment handling + original = $0 + # Trim leading/trailing whitespace + line = $0 + gsub(/^[ \t]+|[ \t]+$/, "", line) + + # Skip empty lines + if (line == "") { next } + + # Skip full comment lines + if (line ~ /^#/) { next } + + # Handle triple-quoted docstrings (both single and double quotes) + if (line ~ /^"""/ || line ~ /"""$/ || line ~ /^'\'''\'''\''/ || line ~ /'\'''\'''\''$/) { + in_docstring = !in_docstring + next + } + if (in_docstring) { next } + + # Remove inline comments + sub(/#.*$/, "", original) + # Trim whitespace after removing inline comment + gsub(/^[ \t]+|[ \t]+$/, "", original) + # Skip if line becomes empty after removing inline comment + if (original == "") { next } + + # Count this line + count++ + } + END { print count } +' {} \; | awk '{total += $1} END {print total}') + +# Verify we got a valid number +if [[ ! "$total_lines" =~ ^[0-9]+$ ]]; then + echo "Error: Failed to count lines properly!" + exit 1 +fi + +echo "Total lines of Python code: ${total_lines}" diff --git a/Pulumi.yaml b/Pulumi.yaml index 5e404a0..ed4321c 100644 --- a/Pulumi.yaml +++ b/Pulumi.yaml @@ -2,9 +2,10 @@ name: konductor description: DevOps Platform IaC Template Repository main: ./pulumi stackConfigDir: ./pulumi/stacks -options: - refresh: always runtime: name: python -# options: -# virtualenv: venv + options: + toolchain: poetry + typechecker: pyright +options: + refresh: always diff --git a/Taskfile.yaml b/Taskfile.yaml index b265fbb..3663288 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -30,6 +30,10 @@ vars: echo "unknown" fi + # New Python-specific vars + python_version: "3.8" + poetry_version: "1.7.1" + tasks: ################################################################################## # Meta & Utility Tasks @@ -39,6 +43,7 @@ tasks: default: desc: "Run all tasks to set up and configure a Kargo Kubevirt Kubernetes Platform." cmds: + - task: dev-setup - task: deploy # Task to print environment variables, useful for debugging @@ -141,21 +146,22 @@ tasks: configure: desc: "Configure Pulumi stack settings." cmds: - - source .envrc && pulumi stack select --create {{.pulumi_stack_identifier}} - - source .envrc && pulumi config set --path kubernetes.context admin@{{.cluster_name}} - - source .envrc && pulumi config set --path kubernetes.kubeconfig {{.kube_config_file}} - - source .envrc && pulumi config set --path kubernetes.distribution talos - - source .envrc && pulumi config set --path cilium.enabled false - - source .envrc && pulumi config set --path vm.enabled false + - source .envrc && poetry run pulumi stack select --create {{.pulumi_stack_identifier}} + - source .envrc && poetry run pulumi config set --path kubernetes.context admin@{{.cluster_name}} + - source .envrc && poetry run pulumi config set --path kubernetes.kubeconfig {{.kube_config_file}} + - source .envrc && poetry run pulumi config set --path kubernetes.distribution talos + - source .envrc && poetry run pulumi config set --path cilium.enabled false + - source .envrc && poetry run pulumi config set --path vm.enabled false # Task to deploy Pulumi infrastructure iac-deploy: desc: "Deploy Pulumi infrastructure." + deps: [type-check] cmds: - task: iac-cancel - - source .envrc && pulumi up --yes --skip-preview --refresh --continue-on-error --stack {{.pulumi_stack_identifier}} || true + - source .envrc && poetry run pulumi up --yes --skip-preview --refresh --continue-on-error --stack {{.pulumi_stack_identifier}} || true - task: all-pods-ready - - source .envrc && pulumi up --yes --skip-preview --refresh --stack {{.pulumi_stack_identifier}} + - source .envrc && poetry run pulumi up --yes --skip-preview --refresh --stack {{.pulumi_stack_identifier}} - task: all-pods-ready # Task to destroy Pulumi infrastructure @@ -165,14 +171,14 @@ tasks: - task: iac-cancel - | source .envrc - pulumi down --yes --skip-preview --refresh --stack {{.pulumi_stack_identifier}} || true - pulumi down --yes --skip-preview --refresh --stack {{.pulumi_stack_identifier}} + poetry run pulumi down --yes --skip-preview --refresh --stack {{.pulumi_stack_identifier}} || true + poetry run pulumi down --yes --skip-preview --refresh --stack {{.pulumi_stack_identifier}} # Task to cancel the Pulumi update process iac-cancel: desc: "Cancel the Pulumi update." cmds: - - source .envrc && pulumi cancel --yes --stack {{.pulumi_stack_identifier}} 2>/dev/null || true + - source .envrc && poetry run pulumi cancel --yes --stack {{.pulumi_stack_identifier}} 2>/dev/null || true # Task to clean up all Pulumi resources iac-clean: @@ -181,6 +187,15 @@ tasks: - task: iac-cancel - source .envrc && pulumi down --yes --skip-preview --refresh --stack {{.pulumi_stack_identifier}} 2>/dev/null || true + # Add pre-commit hook task + pre-commit: + desc: "Run pre-commit checks" + cmds: + - task: type-check + - task: format + - task: lint + - task: test + ################################################################################## # Talos Tasks ################################################################################## @@ -236,3 +251,41 @@ tasks: - task: kubernetes-deploy - task: kubernetes-ready - task: all-pods-ready + + ################################################################################## + # Python Development Tasks + ################################################################################## + + poetry-update: + desc: "Update Poetry dependencies" + cmds: + - poetry update + - poetry install + + type-check: + desc: "Run Pyright type checking" + cmds: + - poetry run pyright pulumi/ + + format: + desc: "Format Python code with Black" + cmds: + - poetry run black pulumi/ + + lint: + desc: "Run Pylint" + cmds: + - poetry run pylint pulumi/ + + test: + desc: "Run Python tests" + cmds: + - poetry run pytest + + dev-setup: + desc: "Complete development environment setup" + cmds: + - task: type-check + - task: format + - task: lint + - task: configure diff --git a/docs/README.md b/docs/README.md index e44e9f2..900c587 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,8 @@ Before you begin, ensure you have the following installed: - **Kubectl**: For interacting with Kubernetes clusters. - **sudo**: For executing administrative commands. +> NOTE: All dependencies are automatically supplied in the [ghcr.io/containercraft/devcontainer](https://github.com/containercraft/devcontainer) image powering the VSCode Dev Container included in this repository by the [.devcontainer/devcontainer.json](.devcontainer/devcontainer.json) and [.devcontainer/Dockerfile](.devcontainer/Dockerfile). + ## Steps to Recreate Follow the steps below to set up your environment: diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..5794117 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1094 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + +[[package]] +name = "arpeggio" +version = "2.0.2" +description = "Packrat parser interpreter" +optional = false +python-versions = "*" +files = [ + {file = "Arpeggio-2.0.2-py2.py3-none-any.whl", hash = "sha256:f7c8ae4f4056a89e020c24c7202ac8df3e2bc84e416746f20b0da35bb1de0250"}, + {file = "Arpeggio-2.0.2.tar.gz", hash = "sha256:c790b2b06e226d2dd468e4fbfb5b7f506cec66416031fde1441cf1de2a0ba700"}, +] + +[package.extras] +dev = ["mike", "mkdocs", "twine", "wheel"] +test = ["coverage", "coveralls", "flake8", "pytest"] + +[[package]] +name = "astroid" +version = "3.2.4" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "black" +version = "23.12.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "dill" +version = "0.3.9" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, + {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "gitdb" +version = "4.0.11" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, + {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.43" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +files = [ + {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, + {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] + +[[package]] +name = "grpcio" +version = "1.66.2" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:fe96281713168a3270878255983d2cb1a97e034325c8c2c25169a69289d3ecfa"}, + {file = "grpcio-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:73fc8f8b9b5c4a03e802b3cd0c18b2b06b410d3c1dcbef989fdeb943bd44aff7"}, + {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:03b0b307ba26fae695e067b94cbb014e27390f8bc5ac7a3a39b7723fed085604"}, + {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d69ce1f324dc2d71e40c9261d3fdbe7d4c9d60f332069ff9b2a4d8a257c7b2b"}, + {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bc2ceadc2529ab0b227b1310d249d95d9001cd106aa4d31e8871ad3c428d73"}, + {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ac475e8da31484efa25abb774674d837b343afb78bb3bcdef10f81a93e3d6bf"}, + {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0be4e0490c28da5377283861bed2941d1d20ec017ca397a5df4394d1c31a9b50"}, + {file = "grpcio-1.66.2-cp310-cp310-win32.whl", hash = "sha256:4e504572433f4e72b12394977679161d495c4c9581ba34a88d843eaf0f2fbd39"}, + {file = "grpcio-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:2018b053aa15782db2541ca01a7edb56a0bf18c77efed975392583725974b249"}, + {file = "grpcio-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:2335c58560a9e92ac58ff2bc5649952f9b37d0735608242973c7a8b94a6437d8"}, + {file = "grpcio-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a3d462826f4868b442a6b8fdbe8b87b45eb4f5b5308168c156b21eca43f61c"}, + {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a9539f01cb04950fd4b5ab458e64a15f84c2acc273670072abe49a3f29bbad54"}, + {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce89f5876662f146d4c1f695dda29d4433a5d01c8681fbd2539afff535da14d4"}, + {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25a14af966438cddf498b2e338f88d1c9706f3493b1d73b93f695c99c5f0e2a"}, + {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6001e575b8bbd89eee11960bb640b6da6ae110cf08113a075f1e2051cc596cae"}, + {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ea1d062c9230278793820146c95d038dc0f468cbdd172eec3363e42ff1c7d01"}, + {file = "grpcio-1.66.2-cp311-cp311-win32.whl", hash = "sha256:38b68498ff579a3b1ee8f93a05eb48dc2595795f2f62716e797dc24774c1aaa8"}, + {file = "grpcio-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:6851de821249340bdb100df5eacfecfc4e6075fa85c6df7ee0eb213170ec8e5d"}, + {file = "grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf"}, + {file = "grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8"}, + {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6"}, + {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7"}, + {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd"}, + {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee"}, + {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c"}, + {file = "grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453"}, + {file = "grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679"}, + {file = "grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d"}, + {file = "grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34"}, + {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed"}, + {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7"}, + {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46"}, + {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a"}, + {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b"}, + {file = "grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75"}, + {file = "grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf"}, + {file = "grpcio-1.66.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:a18e20d8321c6400185b4263e27982488cb5cdd62da69147087a76a24ef4e7e3"}, + {file = "grpcio-1.66.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:02697eb4a5cbe5a9639f57323b4c37bcb3ab2d48cec5da3dc2f13334d72790dd"}, + {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:99a641995a6bc4287a6315989ee591ff58507aa1cbe4c2e70d88411c4dcc0839"}, + {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ed71e81782966ffead60268bbda31ea3f725ebf8aa73634d5dda44f2cf3fb9c"}, + {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbd27c24a4cc5e195a7f56cfd9312e366d5d61b86e36d46bbe538457ea6eb8dd"}, + {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a9724a156c8ec6a379869b23ba3323b7ea3600851c91489b871e375f710bc8"}, + {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d8d4732cc5052e92cea2f78b233c2e2a52998ac40cd651f40e398893ad0d06ec"}, + {file = "grpcio-1.66.2-cp38-cp38-win32.whl", hash = "sha256:7b2c86457145ce14c38e5bf6bdc19ef88e66c5fee2c3d83285c5aef026ba93b3"}, + {file = "grpcio-1.66.2-cp38-cp38-win_amd64.whl", hash = "sha256:e88264caad6d8d00e7913996030bac8ad5f26b7411495848cc218bd3a9040b6c"}, + {file = "grpcio-1.66.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:c400ba5675b67025c8a9f48aa846f12a39cf0c44df5cd060e23fda5b30e9359d"}, + {file = "grpcio-1.66.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:66a0cd8ba6512b401d7ed46bb03f4ee455839957f28b8d61e7708056a806ba6a"}, + {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:06de8ec0bd71be123eec15b0e0d457474931c2c407869b6c349bd9bed4adbac3"}, + {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb57870449dfcfac428afbb5a877829fcb0d6db9d9baa1148705739e9083880e"}, + {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b672abf90a964bfde2d0ecbce30f2329a47498ba75ce6f4da35a2f4532b7acbc"}, + {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad2efdbe90c73b0434cbe64ed372e12414ad03c06262279b104a029d1889d13e"}, + {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c3a99c519f4638e700e9e3f83952e27e2ea10873eecd7935823dab0c1c9250e"}, + {file = "grpcio-1.66.2-cp39-cp39-win32.whl", hash = "sha256:78fa51ebc2d9242c0fc5db0feecc57a9943303b46664ad89921f5079e2e4ada7"}, + {file = "grpcio-1.66.2-cp39-cp39-win_amd64.whl", hash = "sha256:728bdf36a186e7f51da73be7f8d09457a03061be848718d0edf000e709418987"}, + {file = "grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.66.2)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "parver" +version = "0.5" +description = "Parse and manipulate version numbers." +optional = false +python-versions = ">=3.8" +files = [ + {file = "parver-0.5-py3-none-any.whl", hash = "sha256:2281b187276c8e8e3c15634f62287b2fb6fe0efe3010f739a6bd1e45fa2bf2b2"}, + {file = "parver-0.5.tar.gz", hash = "sha256:b9fde1e6bb9ce9f07e08e9c4bea8d8825c5e78e18a0052d02e02bf9517eb4777"}, +] + +[package.dependencies] +arpeggio = ">=1.7" +attrs = ">=19.2" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "sphinx"] +docstest = ["doc8"] +pep8test = ["flake8", "pep8-naming"] +test = ["hypothesis", "pretend", "pytest"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "protobuf" +version = "4.25.5" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-4.25.5-cp310-abi3-win32.whl", hash = "sha256:5e61fd921603f58d2f5acb2806a929b4675f8874ff5f330b7d6f7e2e784bbcd8"}, + {file = "protobuf-4.25.5-cp310-abi3-win_amd64.whl", hash = "sha256:4be0571adcbe712b282a330c6e89eae24281344429ae95c6d85e79e84780f5ea"}, + {file = "protobuf-4.25.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b2fde3d805354df675ea4c7c6338c1aecd254dfc9925e88c6d31a2bcb97eb173"}, + {file = "protobuf-4.25.5-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:919ad92d9b0310070f8356c24b855c98df2b8bd207ebc1c0c6fcc9ab1e007f3d"}, + {file = "protobuf-4.25.5-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fe14e16c22be926d3abfcb500e60cab068baf10b542b8c858fa27e098123e331"}, + {file = "protobuf-4.25.5-cp38-cp38-win32.whl", hash = "sha256:98d8d8aa50de6a2747efd9cceba361c9034050ecce3e09136f90de37ddba66e1"}, + {file = "protobuf-4.25.5-cp38-cp38-win_amd64.whl", hash = "sha256:b0234dd5a03049e4ddd94b93400b67803c823cfc405689688f59b34e0742381a"}, + {file = "protobuf-4.25.5-cp39-cp39-win32.whl", hash = "sha256:abe32aad8561aa7cc94fc7ba4fdef646e576983edb94a73381b03c53728a626f"}, + {file = "protobuf-4.25.5-cp39-cp39-win_amd64.whl", hash = "sha256:7a183f592dc80aa7c8da7ad9e55091c4ffc9497b3054452d629bb85fa27c2a45"}, + {file = "protobuf-4.25.5-py3-none-any.whl", hash = "sha256:0aebecb809cae990f8129ada5ca273d9d670b76d9bfc9b1809f0a9c02b7dbf41"}, + {file = "protobuf-4.25.5.tar.gz", hash = "sha256:7f8249476b4a9473645db7f8ab42b02fe1488cbe5fb72fddd445e0665afd8584"}, +] + +[[package]] +name = "pulumi" +version = "3.33.1" +description = "Pulumi's Python SDK" +optional = false +python-versions = "*" +files = [ + {file = "pulumi-3.33.1-py2.py3-none-any.whl", hash = "sha256:931d838dabb85db663cf359a43d2b0e9f5c0903a488a798de6c57a34ced479b6"}, +] + +[package.dependencies] +dill = ">=0.3.0" +grpcio = ">=1.33.2" +protobuf = ">=3.6.0" +pyyaml = ">=5.3.1" +semver = ">=2.8.1" +six = ">=1.12.0" + +[[package]] +name = "pulumi-aws" +version = "6.55.0" +description = "A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pulumi_aws-6.55.0-py3-none-any.whl", hash = "sha256:955429f259a551781d3c4f49afcc542aac995443192025492bdeb1c24c05a6c1"}, + {file = "pulumi_aws-6.55.0.tar.gz", hash = "sha256:fab3e6139940d25cc64362100b5fa5b5f43f534be90ae549890fcc362ce9e77a"}, +] + +[package.dependencies] +parver = ">=0.2.1" +pulumi = ">=3.0.0,<4.0.0" +semver = ">=2.8.1" +typing-extensions = {version = ">=4.11", markers = "python_version < \"3.11\""} + +[[package]] +name = "pulumi-aws-native" +version = "0.90.0" +description = "A native Pulumi package for creating and managing Amazon Web Services (AWS) resources." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pulumi_aws_native-0.90.0-py3-none-any.whl", hash = "sha256:ee935aa00ca9f402c92522259e6bf90a6d80dac6e3b483155e88f028e590acec"}, + {file = "pulumi_aws_native-0.90.0.tar.gz", hash = "sha256:6ab213d573f1ed788504d9d0a23b13defaa8ecc95ed5593f3bf30aebac199db7"}, +] + +[package.dependencies] +parver = ">=0.2.1" +pulumi = ">=3.0.0,<4.0.0" +semver = ">=2.8.1" + +[[package]] +name = "pulumi-kubernetes" +version = "4.9.0" +description = "A Pulumi package for creating and managing Kubernetes resources." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pulumi_kubernetes-4.9.0-py3-none-any.whl", hash = "sha256:ab03fdf25b0bc15a016502323f017f066fb160dc9705705c73d622679b28ab12"}, + {file = "pulumi_kubernetes-4.9.0.tar.gz", hash = "sha256:0e2c6d45e174b5a05ab1fbd567b6e29fd51bafa199541b786224b777c30c0b58"}, +] + +[package.dependencies] +parver = ">=0.2.1" +pulumi = ">=3.25.0,<4.0.0" +requests = ">=2.21,<3.0" +semver = ">=2.8.1" + +[[package]] +name = "pydantic" +version = "2.9.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = [ + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pylint" +version = "3.2.7" +description = "python code static checker" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, + {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, +] + +[package.dependencies] +astroid = ">=3.2.4,<=3.3.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, +] +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pyright" +version = "1.1.387" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.387-py3-none-any.whl", hash = "sha256:6a1f495a261a72e12ad17e20d1ae3df4511223c773b19407cfa006229b1b08a5"}, + {file = "pyright-1.1.387.tar.gz", hash = "sha256:577de60224f7fe36505d5b181231e3a395d427b7873be0bbcaa962a29ea93a60"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" +typing-extensions = ">=4.1" + +[package.extras] +all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] +nodejs = ["nodejs-wheel-binaries"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "semver" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "smmap" +version = "5.0.1" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +files = [ + {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, + {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, +] + +[[package]] +name = "tomli" +version = "2.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "c148a2c50dd2e9667f7ca0b3c09b851d1ea7fa62a3e3507f3a182b5597ebc66b" diff --git a/pulumi/__main__.py b/pulumi/__main__.py index 32d49db..6530c53 100644 --- a/pulumi/__main__.py +++ b/pulumi/__main__.py @@ -1,9 +1,11 @@ # pulumi/__main__.py -from pulumi import log +import pulumi + from core.config import export_results from core.deployment import initialize_pulumi, deploy_modules + def main(): try: # Initialize Pulumi @@ -30,8 +32,8 @@ def main(): # - Modules are set to enabled=false by defualt, override this to enable by default in DEFAULT_ENABLED_CONFIG. modules_to_deploy = [ "aws", - #"cert_manager", - #"prometheus" + # "cert_manager", + # "prometheus" ] # Deploy modules @@ -45,16 +47,17 @@ def main(): k8s_provider, versions, configurations, - compliance_config + compliance_config, ) # Export stack outputs. export_results(versions, configurations, compliance_config) except Exception as e: - log.error(f"Deployment failed: {str(e)}") + pulumi.log.error(f"Deployment failed: {str(e)}") raise + # Entry point for the Pulumi program. # TODO: # - Re-evaluate structure and best location for export_results function call. diff --git a/pulumi/core/config.py b/pulumi/core/config.py index 0362b9f..94f7eda 100644 --- a/pulumi/core/config.py +++ b/pulumi/core/config.py @@ -19,11 +19,12 @@ import os import pulumi import requests -from typing import Any, Dict, Tuple, Optional -from .types import ComplianceConfig +from typing import Any, Dict, Tuple # Default versions URL template -DEFAULT_VERSIONS_URL_TEMPLATE = 'https://raw.githubusercontent.com/ContainerCraft/Kargo/rerefactor/pulumi/' +DEFAULT_VERSIONS_URL_TEMPLATE = ( + "https://raw.githubusercontent.com/ContainerCraft/Kargo/rerefactor/pulumi/" +) # Module enabled defaults: Setting a module to True enables the module by default DEFAULT_ENABLED_CONFIG = { @@ -36,6 +37,7 @@ "prometheus": True, } + def coerce_to_bool(value: Any) -> bool: """ Coerces a value to a boolean. @@ -49,14 +51,15 @@ def coerce_to_bool(value: Any) -> bool: if isinstance(value, bool): return value if isinstance(value, str): - return value.lower() == 'true' + return value.lower() == "true" return bool(value) + def get_module_config( - module_name: str, - config: pulumi.Config, - default_versions: Dict[str, Any], - ) -> Tuple[Dict[str, Any], bool]: + module_name: str, + config: pulumi.Config, + default_versions: Dict[str, Any], +) -> Tuple[Dict[str, Any], bool]: """ Retrieves and prepares the configuration for a module. @@ -73,21 +76,28 @@ def get_module_config( module_config = config.get_object(module_name) or {} # Retrieve enabled status from configuration or defaults to defined default setting - enabled_value = module_config.pop('enabled', DEFAULT_ENABLED_CONFIG.get(module_name, False)) + enabled_value = module_config.pop( + "enabled", DEFAULT_ENABLED_CONFIG.get(module_name, False) + ) module_enabled = coerce_to_bool(enabled_value) # Include 'compliance' config into 'module_config' for AWS module - if module_name == 'aws': - compliance_config_dict = config.get_object('compliance') or {} - module_config['compliance'] = compliance_config_dict + if module_name == "aws": + compliance_config_dict = config.get_object("compliance") or {} + module_config["compliance"] = compliance_config_dict # Only set the version if it is *not* the aws module if module_name != "aws": - module_config['version'] = module_config.get('version', default_versions.get(module_name)) + module_config["version"] = module_config.get( + "version", default_versions.get(module_name) + ) return module_config, module_enabled -def load_default_versions(config: pulumi.Config, force_refresh: bool = False) -> Dict[str, Any]: + +def load_default_versions( + config: pulumi.Config, force_refresh: bool = False +) -> Dict[str, Any]: """ Loads the default versions for modules based on the specified configuration settings. @@ -107,7 +117,7 @@ def load_default_versions(config: pulumi.Config, force_refresh: bool = False) -> Raises: Exception: If default versions cannot be loaded from any source. """ - cache_file = '/tmp/default_versions.json' + cache_file = "/tmp/default_versions.json" if not force_refresh and os.path.exists(cache_file): try: with open(cache_file) as f: @@ -116,15 +126,15 @@ def load_default_versions(config: pulumi.Config, force_refresh: bool = False) -> pulumi.log.warn(f"Error reading cache file: {e}") stack_name = pulumi.get_stack() - default_versions_source = config.get('default_versions.source') - versions_channel = config.get('versions.channel') or 'stable' - versions_stack_name = coerce_to_bool(config.get('versions.stack_name')) or False + default_versions_source = config.get("default_versions.source") + versions_channel = config.get("versions.channel") or "stable" + versions_stack_name = coerce_to_bool(config.get("versions.stack_name")) or False default_versions = {} # Function to try loading default versions from file def load_versions_from_file(file_path: str) -> dict: try: - with open(file_path, 'r') as f: + with open(file_path, "r") as f: versions = json.load(f) pulumi.log.info(f"Loaded default versions from file: {file_path}") return versions @@ -144,42 +154,49 @@ def load_versions_from_url(url: str) -> dict: return {} if default_versions_source: - if default_versions_source.startswith(('http://', 'https://')): + if default_versions_source.startswith(("http://", "https://")): default_versions = load_versions_from_url(default_versions_source) else: default_versions = load_versions_from_file(default_versions_source) if not default_versions: - raise Exception(f"Failed to load default versions from specified source: {default_versions_source}") + raise Exception( + f"Failed to load default versions from specified source: {default_versions_source}" + ) else: if versions_stack_name: current_dir = os.path.dirname(os.path.abspath(__file__)) - stack_versions_path = os.path.join(current_dir, '..', 'versions', f'{stack_name}.json') + stack_versions_path = os.path.join( + current_dir, "..", "versions", f"{stack_name}.json" + ) default_versions = load_versions_from_file(stack_versions_path) if not default_versions: current_dir = os.path.dirname(os.path.abspath(__file__)) - default_versions_path = os.path.join(current_dir, '..', 'default_versions.json') + default_versions_path = os.path.join( + current_dir, "..", "default_versions.json" + ) default_versions = load_versions_from_file(default_versions_path) if not default_versions: - versions_url = f'{DEFAULT_VERSIONS_URL_TEMPLATE}{versions_channel}_versions.json' + versions_url = ( + f"{DEFAULT_VERSIONS_URL_TEMPLATE}{versions_channel}_versions.json" + ) default_versions = load_versions_from_url(versions_url) if not default_versions: raise Exception("Cannot proceed without default versions.") - with open(cache_file, 'w') as f: + with open(cache_file, "w") as f: json.dump(default_versions, f) return default_versions + def export_results( - versions: Dict[str, str], - configurations: Dict[str, Dict[str, Any]], - compliance: Any - ): + versions: Dict[str, str], configurations: Dict[str, Dict[str, Any]], compliance: Any +): """ Exports the results of the deployment processes including versions, configurations, and compliance information. @@ -189,7 +206,7 @@ def export_results( compliance (Any): The compliance configuration, can be ComplianceConfig or a dictionary. """ # Convert compliance to a dictionary if it's a Pydantic model - if hasattr(compliance, 'dict'): + if hasattr(compliance, "dict"): compliance_dict = compliance.dict() else: compliance_dict = compliance diff --git a/pulumi/core/deployment.py b/pulumi/core/deployment.py index a27491e..8e00a91 100644 --- a/pulumi/core/deployment.py +++ b/pulumi/core/deployment.py @@ -26,11 +26,12 @@ set_global_labels, set_global_annotations, generate_compliance_labels, - generate_compliance_annotations + generate_compliance_annotations, ) from .utils import generate_global_transformations from .types import ComplianceConfig + def initialize_pulumi() -> Dict[str, Any]: """ Initializes Pulumi configuration, Kubernetes provider, and global resources. @@ -55,7 +56,7 @@ def initialize_pulumi() -> Dict[str, Any]: kubernetes_config = config.get_object("kubernetes") or {} kubernetes_context = kubernetes_config.get("context") - kubeconfig = kubernetes_config.get("kubeconfig") or os.getenv('KUBECONFIG') + kubeconfig = kubernetes_config.get("kubeconfig") or os.getenv("KUBECONFIG") # Initialize the Kubernetes provider. k8s_provider = Provider( @@ -75,11 +76,11 @@ def initialize_pulumi() -> Dict[str, Any]: configurations["source_repository"] = { "remote": git_info["remote"], "branch": git_info["branch"], - "commit": git_info["commit"] + "commit": git_info["commit"], } # Retrieve compliance metadata from pulumi configuration. - compliance_config_dict = config.get_object('compliance') or {} + compliance_config_dict = config.get_object("compliance") or {} compliance_config = ComplianceConfig.merge(compliance_config_dict) pulumi.log.info(f"Compliance Config: {compliance_config}") @@ -114,6 +115,7 @@ def initialize_pulumi() -> Dict[str, Any]: log.error(f"Initialization error: {str(e)}") raise + def deploy_module( module_name: str, config: pulumi.Config, @@ -146,14 +148,18 @@ def deploy_module( if not isinstance(global_depends_on, list): raise TypeError("global_depends_on must be a list") if not isinstance(k8s_provider, k8s.Provider): - raise TypeError("k8s_provider must be an instance of pulumi_kubernetes.Provider") + raise TypeError( + "k8s_provider must be an instance of pulumi_kubernetes.Provider" + ) if not isinstance(versions, dict): raise TypeError("versions must be a dictionary") if not isinstance(configurations, dict): raise TypeError("configurations must be a dictionary") # Retrieve module configuration and enabled status. - module_config_dict, module_enabled = get_module_config(module_name, config, default_versions) + module_config_dict, module_enabled = get_module_config( + module_name, config, default_versions + ) if module_enabled: ModuleConfigClass = discover_config_class(module_name) @@ -164,9 +170,12 @@ def deploy_module( deploy_func_args = inspect.signature(deploy_func).parameters.keys() config_arg_name = list(deploy_func_args)[0] - deploy_kwargs = {config_arg_name: config_obj, "global_depends_on": global_depends_on} + deploy_kwargs = { + config_arg_name: config_obj, + "global_depends_on": global_depends_on, + } - if module_name != 'aws': + if module_name != "aws": deploy_kwargs["k8s_provider"] = k8s_provider try: @@ -178,7 +187,9 @@ def deploy_module( version, release = result module_aux_meta = None else: - raise ValueError(f"Unexpected return value structure from {module_name} deploy function") + raise ValueError( + f"Unexpected return value structure from {module_name} deploy function" + ) versions[module_name] = version configurations[module_name] = {"enabled": module_enabled} @@ -195,6 +206,7 @@ def deploy_module( else: log.info(f"Module {module_name} is not enabled.") + def discover_config_class(module_name: str) -> Type: """ Discovers and returns the configuration class from the module's types.py. @@ -224,6 +236,7 @@ def discover_config_class(module_name: str) -> Type: raise ValueError(f"No configuration class found in modules.{module_name}.types") + def discover_deploy_function(module_name: str) -> Callable: """ Discovers and returns the deploy function from the module's deploy.py. @@ -238,19 +251,22 @@ def discover_deploy_function(module_name: str) -> Callable: function_name = f"deploy_{module_name}_module" deploy_function = getattr(deploy_module, function_name, None) if not deploy_function: - raise ValueError(f"No deploy function named '{function_name}' found in modules.{module_name}.deploy") + raise ValueError( + f"No deploy function named '{function_name}' found in modules.{module_name}.deploy" + ) return deploy_function + def deploy_modules( - modules: List[str], - config: pulumi.Config, - default_versions: Dict[str, Any], - global_depends_on: List[pulumi.Resource], - k8s_provider: Provider, - versions: Dict[str, str], - configurations: Dict[str, Dict[str, Any]], - compliance_config: ComplianceConfig, - ) -> None: + modules: List[str], + config: pulumi.Config, + default_versions: Dict[str, Any], + global_depends_on: List[pulumi.Resource], + k8s_provider: Provider, + versions: Dict[str, str], + configurations: Dict[str, Dict[str, Any]], + compliance_config: ComplianceConfig, +) -> None: """ Iterates over a list of modules and deploys each configured and enabled module. diff --git a/pulumi/core/metadata.py b/pulumi/core/metadata.py index 12e3dbb..14a29b8 100644 --- a/pulumi/core/metadata.py +++ b/pulumi/core/metadata.py @@ -23,6 +23,7 @@ from .types import ComplianceConfig + # Singleton class to manage global metadata # Globals are correctly chosen to enforce consistency across all modules and resources # This class is thread-safe and used to store global labels and annotations @@ -35,9 +36,13 @@ def __new__(cls, *args, **kwargs): with cls.__lock: if not cls._instance: cls._instance = super(MetadataSingleton, cls).__new__(cls) - cls._instance._data = {"_global_labels": {}, "_global_annotations": {}} + cls._instance._data = { + "_global_labels": {}, + "_global_annotations": {}, + } return cls._instance + def set_global_labels(labels: Dict[str, str]): """ Sets global labels. @@ -47,6 +52,7 @@ def set_global_labels(labels: Dict[str, str]): """ MetadataSingleton()._data["_global_labels"] = labels + def set_global_annotations(annotations: Dict[str, str]): """ Sets global annotations. @@ -56,6 +62,7 @@ def set_global_annotations(annotations: Dict[str, str]): """ MetadataSingleton()._data["_global_annotations"] = annotations + def get_global_labels() -> Dict[str, str]: """ Retrieves global labels. @@ -65,6 +72,7 @@ def get_global_labels() -> Dict[str, str]: """ return MetadataSingleton()._data["_global_labels"] + def get_global_annotations() -> Dict[str, str]: """ Retrieves global annotations. @@ -74,6 +82,7 @@ def get_global_annotations() -> Dict[str, str]: """ return MetadataSingleton()._data["_global_annotations"] + def generate_git_labels(git_info: Dict[str, str]) -> Dict[str, str]: """ Generates git-related labels suitable for AWS tags. @@ -95,6 +104,7 @@ def generate_git_labels(git_info: Dict[str, str]) -> Dict[str, str]: return sanitized_labels + def generate_git_annotations(git_info: Dict[str, str]) -> Dict[str, str]: """ Generates git-related annotations. @@ -108,9 +118,10 @@ def generate_git_annotations(git_info: Dict[str, str]) -> Dict[str, str]: return { "git.remote": git_info.get("remote", ""), "git.commit.full": git_info.get("commit", ""), - "git.branch": git_info.get("branch", "") + "git.branch": git_info.get("branch", ""), } + def generate_compliance_labels(compliance_config: ComplianceConfig) -> Dict[str, str]: """ Generates compliance labels based on the given compliance configuration. @@ -122,7 +133,7 @@ def generate_compliance_labels(compliance_config: ComplianceConfig) -> Dict[str, Dict[str, str]: A dictionary of compliance labels. """ compliance_dict = compliance_config.dict() - flattened_compliance = flatten_dict(compliance_dict, list_sep=':') + flattened_compliance = flatten_dict(compliance_dict, list_sep=":") sanitized_labels = {} for key, value in flattened_compliance.items(): @@ -132,7 +143,10 @@ def generate_compliance_labels(compliance_config: ComplianceConfig) -> Dict[str, return sanitized_labels -def generate_compliance_annotations(compliance_config: ComplianceConfig) -> Dict[str, str]: + +def generate_compliance_annotations( + compliance_config: ComplianceConfig, +) -> Dict[str, str]: """ Generates compliance annotations based on the given compliance configuration. @@ -146,17 +160,24 @@ def generate_compliance_annotations(compliance_config: ComplianceConfig) -> Dict # TODO: enhance if logic to improve efficiency, DRY, readability and maintainability annotations = {} if compliance_config.fisma.level: - annotations['compliance.fisma.level'] = compliance_config.fisma.level + annotations["compliance.fisma.level"] = compliance_config.fisma.level if compliance_config.fisma.ato: - annotations['compliance.fisma.ato'] = json.dumps(compliance_config.fisma.ato) + annotations["compliance.fisma.ato"] = json.dumps(compliance_config.fisma.ato) if compliance_config.nist.controls: - annotations['compliance.nist.controls'] = json.dumps(compliance_config.nist.controls) + annotations["compliance.nist.controls"] = json.dumps( + compliance_config.nist.controls + ) if compliance_config.nist.auxiliary: - annotations['compliance.nist.auxiliary'] = json.dumps(compliance_config.nist.auxiliary) + annotations["compliance.nist.auxiliary"] = json.dumps( + compliance_config.nist.auxiliary + ) if compliance_config.nist.exceptions: - annotations['compliance.nist.exceptions'] = json.dumps(compliance_config.nist.exceptions) + annotations["compliance.nist.exceptions"] = json.dumps( + compliance_config.nist.exceptions + ) return annotations + # Function to sanitize a label value to comply with Kubernetes `label` naming conventions # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set # TODO: @@ -172,12 +193,13 @@ def sanitize_label_value(value: str) -> str: str: The sanitized value. """ value = value.lower() - sanitized = re.sub(r'[^a-z0-9_.-]', '-', value) - sanitized = re.sub(r'^[^a-z0-9]+', '', sanitized) - sanitized = re.sub(r'[^a-z0-9]+$', '', sanitized) + sanitized = re.sub(r"[^a-z0-9_.-]", "-", value) + sanitized = re.sub(r"^[^a-z0-9]+", "", sanitized) + sanitized = re.sub(r"[^a-z0-9]+$", "", sanitized) return sanitized[:63] -def flatten_dict(data, parent_key='', sep='.', list_sep=':') -> Dict[str, str]: + +def flatten_dict(data, parent_key="", sep=".", list_sep=":") -> Dict[str, str]: """ Flattens a nested dictionary into a single-level dictionary with concatenated keys. @@ -201,6 +223,7 @@ def flatten_dict(data, parent_key='', sep='.', list_sep=':') -> Dict[str, str]: items.append((new_key, str(v))) return dict(items) + def sanitize_tag_key(key: str) -> str: """ Sanitizes a string to be used as an AWS tag key. @@ -212,9 +235,10 @@ def sanitize_tag_key(key: str) -> str: str: The sanitized key. """ # AWS tag key must be 1-128 Unicode characters - sanitized = re.sub(r'[^a-zA-Z0-9\s_.:/=+\-@]', '-', key) + sanitized = re.sub(r"[^a-zA-Z0-9\s_.:/=+\-@]", "-", key) return sanitized[:128] + def sanitize_tag_value(value: str) -> str: """ Sanitizes a string to be used as an AWS tag value. @@ -227,9 +251,10 @@ def sanitize_tag_value(value: str) -> str: """ # AWS tag value must be 0-256 Unicode characters # Include colons ':' in the allowed characters - sanitized = re.sub(r'[^a-zA-Z0-9\s_./:=+\-@]', '-', value) + sanitized = re.sub(r"[^a-zA-Z0-9\s_./:=+\-@]", "-", value) return sanitized[:256] + def collect_git_info() -> Dict[str, str]: """ Collects Git repository information using GitPython. @@ -248,14 +273,14 @@ def collect_git_info() -> Dict[str, str]: - dirty: Whether working tree has uncommitted changes """ git_info = { - 'remote': 'N/A', - 'branch': 'N/A', - 'commit': 'N/A', - 'commit_short': 'N/A', - 'commit_date': 'N/A', - 'latest_tag': 'N/A', - 'latest_release': 'N/A', - 'dirty': 'false' + "remote": "N/A", + "branch": "N/A", + "commit": "N/A", + "commit_short": "N/A", + "commit_date": "N/A", + "latest_tag": "N/A", + "latest_release": "N/A", + "dirty": "false", } try: @@ -265,27 +290,29 @@ def collect_git_info() -> Dict[str, str]: # Get remote URL (try multiple methods) try: remote_url = get_remote_url(repo) - git_info['remote'] = remote_url + git_info["remote"] = remote_url except Exception as e: log.warn(f"Failed to get remote URL: {str(e)}") # Get current branch try: - git_info['branch'] = repo.active_branch.name + git_info["branch"] = repo.active_branch.name except TypeError: # Handle detached HEAD state - git_info['branch'] = 'HEAD' + git_info["branch"] = "HEAD" except Exception as e: log.warn(f"Failed to get branch name: {str(e)}") # Get commit information try: commit = repo.head.commit - git_info.update({ - 'commit': commit.hexsha, - 'commit_short': commit.hexsha[:8], - 'commit_date': commit.committed_datetime.isoformat(), - }) + git_info.update( + { + "commit": commit.hexsha, + "commit_short": commit.hexsha[:8], + "commit_date": commit.committed_datetime.isoformat(), + } + ) except Exception as e: log.warn(f"Failed to get commit information: {str(e)}") @@ -293,13 +320,15 @@ def collect_git_info() -> Dict[str, str]: try: latest_tag = get_latest_semver_tag(repo) if latest_tag: - git_info['latest_tag'] = latest_tag - git_info['latest_release'] = str(semver.VersionInfo.parse(latest_tag.lstrip('v'))) + git_info["latest_tag"] = latest_tag + git_info["latest_release"] = str( + semver.VersionInfo.parse(latest_tag.lstrip("v")) + ) except Exception as e: log.warn(f"Failed to get tag/release information: {str(e)}") # Check if working tree is dirty - git_info['dirty'] = str(repo.is_dirty()).lower() + git_info["dirty"] = str(repo.is_dirty()).lower() log.info(f"Successfully collected git info: {git_info}") @@ -311,6 +340,7 @@ def collect_git_info() -> Dict[str, str]: return git_info + def get_remote_url(repo: git.Repo) -> str: """ Gets the remote URL using multiple fallback methods. @@ -323,22 +353,23 @@ def get_remote_url(repo: git.Repo) -> str: """ # Try getting from origin remote try: - return next(remote.url for remote in repo.remotes if remote.name == 'origin') + return next(remote.url for remote in repo.remotes if remote.name == "origin") except (StopIteration, AttributeError): pass # Try getting from git config try: - return repo.git.config('--get', 'remote.origin.url') + return repo.git.config("--get", "remote.origin.url") except git.exc.GitCommandError: pass # Try environment variables (useful in CI/CD) - for env_var in ['CI_REPOSITORY_URL', 'GITHUB_REPOSITORY', 'GIT_URL']: + for env_var in ["CI_REPOSITORY_URL", "GITHUB_REPOSITORY", "GIT_URL"]: if url := os.getenv(env_var): return url - return 'N/A' + return "N/A" + def get_latest_semver_tag(repo: git.Repo) -> Optional[str]: """ @@ -359,7 +390,7 @@ def get_latest_semver_tag(repo: git.Repo) -> Optional[str]: semver_tags = [] for tag in tags: # Remove 'v' prefix if present - version_str = tag.lstrip('v') + version_str = tag.lstrip("v") try: # Parse version and add to list if valid version = semver.VersionInfo.parse(version_str) @@ -376,6 +407,7 @@ def get_latest_semver_tag(repo: git.Repo) -> Optional[str]: return None + def sanitize_git_info(git_info: Dict[str, str]) -> Dict[str, str]: """ Sanitizes git information for use in resource tags/labels. @@ -390,13 +422,13 @@ def sanitize_git_info(git_info: Dict[str, str]) -> Dict[str, str]: sanitized = {} for key, value in git_info.items(): # Convert to lowercase and replace invalid characters - sanitized_value = re.sub(r'[^a-z0-9-._]', '-', str(value).lower()) + sanitized_value = re.sub(r"[^a-z0-9-._]", "-", str(value).lower()) # Trim to maximum allowed length (63 chars for k8s labels) sanitized_value = sanitized_value[:63] # Remove leading/trailing non-alphanumeric characters - sanitized_value = re.sub(r'^[^a-z0-9]+|[^a-z0-9]+$', '', sanitized_value) + sanitized_value = re.sub(r"^[^a-z0-9]+|[^a-z0-9]+$", "", sanitized_value) sanitized[key] = sanitized_value diff --git a/pulumi/core/resource_helpers.py b/pulumi/core/resource_helpers.py index 2310392..cc9ceeb 100644 --- a/pulumi/core/resource_helpers.py +++ b/pulumi/core/resource_helpers.py @@ -6,6 +6,7 @@ from .metadata import get_global_labels, get_global_annotations from .utils import set_resource_metadata + def create_namespace( name: str, labels: Optional[Dict[str, str]] = None, @@ -91,6 +92,7 @@ def create_namespace( opts=opts, ) + def create_custom_resource( name: str, args: Dict[str, Any], @@ -112,8 +114,10 @@ def create_custom_resource( k8s.apiextensions.CustomResource: The created CustomResource. """ try: - if 'kind' not in args or 'apiVersion' not in args: - raise ValueError("The 'args' dictionary must include 'kind' and 'apiVersion' keys.") + if "kind" not in args or "apiVersion" not in args: + raise ValueError( + "The 'args' dictionary must include 'kind' and 'apiVersion' keys." + ) if opts is None: opts = pulumi.ResourceOptions() @@ -125,8 +129,10 @@ def create_custom_resource( def custom_resource_transform(resource_args: pulumi.ResourceTransformationArgs): props = resource_args.props - if 'metadata' in props: - set_resource_metadata(props['metadata'], global_labels, global_annotations) + if "metadata" in props: + set_resource_metadata( + props["metadata"], global_labels, global_annotations + ) return pulumi.ResourceTransformationResult(props, resource_args.opts) opts = pulumi.ResourceOptions.merge( @@ -139,13 +145,13 @@ def custom_resource_transform(resource_args: pulumi.ResourceTransformationArgs): ) # Ensure metadata and spec are included if specified - metadata = args.get('metadata', {}) - spec = args.get('spec', {}) + metadata = args.get("metadata", {}) + spec = args.get("spec", {}) return k8s.apiextensions.CustomResource( resource_name=name, - api_version=args['apiVersion'], - kind=args['kind'], + api_version=args["apiVersion"], + kind=args["kind"], metadata=metadata, spec=spec, opts=opts, @@ -155,11 +161,19 @@ def custom_resource_transform(resource_args: pulumi.ResourceTransformationArgs): pulumi.log.error(f"Failed to create custom resource '{name}': {e}") raise + def create_helm_release( name: str, args: k8s.helm.v3.ReleaseArgs, opts: Optional[pulumi.ResourceOptions] = None, - transformations: Optional[List[Callable[[pulumi.ResourceTransformationArgs], Optional[pulumi.ResourceTransformationResult]]]] = None, + transformations: Optional[ + List[ + Callable[ + [pulumi.ResourceTransformationArgs], + Optional[pulumi.ResourceTransformationResult], + ] + ] + ] = None, k8s_provider: Optional[k8s.Provider] = None, depends_on: Optional[List[pulumi.Resource]] = None, ) -> k8s.helm.v3.Release: @@ -189,11 +203,13 @@ def create_helm_release( def helm_resource_transform(resource_args: pulumi.ResourceTransformationArgs): props = resource_args.props - if 'metadata' in props: - set_resource_metadata(props['metadata'], global_labels, global_annotations) - elif 'spec' in props and isinstance(props['spec'], dict): - if 'metadata' in props['spec']: - set_resource_metadata(props['spec']['metadata'], global_labels, global_annotations) + if "metadata" in props: + set_resource_metadata(props["metadata"], global_labels, global_annotations) + elif "spec" in props and isinstance(props["spec"], dict): + if "metadata" in props["spec"]: + set_resource_metadata( + props["spec"]["metadata"], global_labels, global_annotations + ) return pulumi.ResourceTransformationResult(props, resource_args.opts) transformations.append(helm_resource_transform) @@ -209,6 +225,7 @@ def helm_resource_transform(resource_args: pulumi.ResourceTransformationArgs): return k8s.helm.v3.Release(name, args, opts=opts) + def create_secret( name: str, args: Dict[str, Any], @@ -240,8 +257,8 @@ def create_secret( def secret_resource_transform(resource_args: pulumi.ResourceTransformationArgs): props = resource_args.props - if 'metadata' in props: - set_resource_metadata(props['metadata'], global_labels, global_annotations) + if "metadata" in props: + set_resource_metadata(props["metadata"], global_labels, global_annotations) return pulumi.ResourceTransformationResult(props, resource_args.opts) # Merge resource options @@ -257,11 +274,19 @@ def secret_resource_transform(resource_args: pulumi.ResourceTransformationArgs): # Constructor call return k8s.core.v1.Secret(name, opts, **args) + def create_config_file( name: str, file: str, opts: Optional[pulumi.ResourceOptions] = None, - transformations: Optional[List[Callable[[pulumi.ResourceTransformationArgs], Optional[pulumi.ResourceTransformationResult]]]] = None, + transformations: Optional[ + List[ + Callable[ + [pulumi.ResourceTransformationArgs], + Optional[pulumi.ResourceTransformationResult], + ] + ] + ] = None, k8s_provider: Optional[k8s.Provider] = None, depends_on: Optional[List[pulumi.Resource]] = None, ) -> k8s.yaml.ConfigFile: @@ -291,11 +316,13 @@ def create_config_file( def config_file_transform(resource_args: pulumi.ResourceTransformationArgs): props = resource_args.props - if 'metadata' in props: - set_resource_metadata(props['metadata'], global_labels, global_annotations) - elif 'spec' in props and isinstance(props['spec'], dict): - if 'metadata' in props['spec']: - set_resource_metadata(props['spec']['metadata'], global_labels, global_annotations) + if "metadata" in props: + set_resource_metadata(props["metadata"], global_labels, global_annotations) + elif "spec" in props and isinstance(props["spec"], dict): + if "metadata" in props["spec"]: + set_resource_metadata( + props["spec"]["metadata"], global_labels, global_annotations + ) return pulumi.ResourceTransformationResult(props, resource_args.opts) transformations.append(config_file_transform) @@ -311,6 +338,7 @@ def config_file_transform(resource_args: pulumi.ResourceTransformationArgs): return k8s.yaml.ConfigFile(name, file, opts=opts) + # ------------------------------------------------------------------------------ # Metadata # ------------------------------------------------------------------------------ diff --git a/pulumi/core/types.py b/pulumi/core/types.py index 66cd84d..1cfdf95 100644 --- a/pulumi/core/types.py +++ b/pulumi/core/types.py @@ -9,6 +9,7 @@ from typing import Optional, List, Dict, Any from pydantic import BaseModel, validator + class NamespaceConfig(BaseModel): name: str labels: Dict[str, str] = {"ccio.v1/app": "kargo"} @@ -17,52 +18,48 @@ class NamespaceConfig(BaseModel): protect: bool = False retain_on_delete: bool = False ignore_changes: List[str] = ["metadata", "spec"] - custom_timeouts: Dict[str, str] = { - "create": "5m", - "update": "10m", - "delete": "10m" - } + custom_timeouts: Dict[str, str] = {"create": "5m", "update": "10m", "delete": "10m"} + class FismaConfig(BaseModel): enabled: bool = False level: Optional[str] = None ato: Dict[str, str] = {} - @validator('enabled', pre=True) + @validator("enabled", pre=True) def parse_enabled(cls, v): if isinstance(v, str): - return v.lower() == 'true' + return v.lower() == "true" return bool(v) + class NistConfig(BaseModel): enabled: bool = False controls: List[str] = [] auxiliary: List[str] = [] exceptions: List[str] = [] - @validator('enabled', pre=True) + @validator("enabled", pre=True) def parse_enabled(cls, v): if isinstance(v, str): - return v.lower() == 'true' + return v.lower() == "true" return bool(v) + class ScipConfig(BaseModel): environment: Optional[str] = None ownership: Dict[str, Any] = {} provider: Dict[str, Any] = {} + class ComplianceConfig(BaseModel): fisma: FismaConfig = FismaConfig() nist: NistConfig = NistConfig() scip: ScipConfig = ScipConfig() @classmethod - def merge(cls, user_config: Dict[str, Any]) -> 'ComplianceConfig': - fisma_config = FismaConfig(**user_config.get('fisma', {})) - nist_config = NistConfig(**user_config.get('nist', {})) - scip_config = ScipConfig(**user_config.get('scip', {})) - return cls( - fisma=fisma_config, - nist=nist_config, - scip=scip_config - ) + def merge(cls, user_config: Dict[str, Any]) -> "ComplianceConfig": + fisma_config = FismaConfig(**user_config.get("fisma", {})) + nist_config = NistConfig(**user_config.get("nist", {})) + scip_config = ScipConfig(**user_config.get("scip", {})) + return cls(fisma=fisma_config, nist=nist_config, scip=scip_config) diff --git a/pulumi/core/utils.py b/pulumi/core/utils.py index 28c9a9a..794025f 100644 --- a/pulumi/core/utils.py +++ b/pulumi/core/utils.py @@ -20,16 +20,21 @@ # Set up basic logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) + # Function to update global resource tags, labels, and annotations from compliance config spec -def set_resource_metadata(metadata: Any, global_labels: Dict[str, str], global_annotations: Dict[str, str]): +def set_resource_metadata( + metadata: Any, global_labels: Dict[str, str], global_annotations: Dict[str, str] +): """ Updates resource metadata with global labels and annotations. """ if isinstance(metadata, dict): - metadata.setdefault('labels', {}).update(global_labels) - metadata.setdefault('annotations', {}).update(global_annotations) + metadata.setdefault("labels", {}).update(global_labels) + metadata.setdefault("annotations", {}).update(global_annotations) elif isinstance(metadata, k8s.meta.v1.ObjectMetaArgs): if metadata.labels is None: metadata.labels = {} @@ -38,24 +43,33 @@ def set_resource_metadata(metadata: Any, global_labels: Dict[str, str], global_a metadata.annotations = {} metadata.annotations.update(global_annotations) + # Function to apply global resource tags, labels, and annotations to all yaml objects -def generate_global_transformations(global_labels: Dict[str, str], global_annotations: Dict[str, str]): +def generate_global_transformations( + global_labels: Dict[str, str], global_annotations: Dict[str, str] +): """ Generates global transformations for resources. """ - def global_transform(args: pulumi.ResourceTransformationArgs) -> Optional[pulumi.ResourceTransformationResult]: + + def global_transform( + args: pulumi.ResourceTransformationArgs, + ) -> Optional[pulumi.ResourceTransformationResult]: props = args.props - if 'metadata' in props: - set_resource_metadata(props['metadata'], global_labels, global_annotations) - elif 'spec' in props and isinstance(props['spec'], dict): - if 'metadata' in props['spec']: - set_resource_metadata(props['spec']['metadata'], global_labels, global_annotations) + if "metadata" in props: + set_resource_metadata(props["metadata"], global_labels, global_annotations) + elif "spec" in props and isinstance(props["spec"], dict): + if "metadata" in props["spec"]: + set_resource_metadata( + props["spec"]["metadata"], global_labels, global_annotations + ) return pulumi.ResourceTransformationResult(props, args.opts) pulumi.runtime.register_stack_transformation(global_transform) + # Function to fetch the latest stable version of a Helm chart from a helm chart index.yaml url def get_latest_helm_chart_version(repo_url: str, chart_name: str) -> str: """ @@ -69,21 +83,25 @@ def get_latest_helm_chart_version(repo_url: str, chart_name: str) -> str: str: The latest stable version of the chart. """ try: - index_url = repo_url.rstrip('/') + '/index.yaml' + index_url = repo_url.rstrip("/") + "/index.yaml" logging.info(f"Fetching Helm repository index from URL: {index_url}") response = requests.get(index_url) response.raise_for_status() index = yaml.safe_load(response.content) - if chart_name in index['entries']: - chart_versions = index['entries'][chart_name] - stable_versions = [v for v in chart_versions if is_stable_version(v['version'])] + if chart_name in index["entries"]: + chart_versions = index["entries"][chart_name] + stable_versions = [ + v for v in chart_versions if is_stable_version(v["version"]) + ] if not stable_versions: logging.info(f"No stable versions found for chart '{chart_name}'.") return "Chart not found" - latest_chart = max(stable_versions, key=lambda x: parse_version(x['version'])) - return latest_chart['version'].lstrip('v') + latest_chart = max( + stable_versions, key=lambda x: parse_version(x["version"]) + ) + return latest_chart["version"].lstrip("v") else: logging.info(f"No chart named '{chart_name}' found in repository.") return "Chart not found" @@ -95,6 +113,7 @@ def get_latest_helm_chart_version(repo_url: str, chart_name: str) -> str: logging.error(f"Error parsing Helm repository index YAML: {e}") return f"Error parsing YAML: {e}" + # Sanity check Helm chart versions for stable releases def is_stable_version(version_str: str) -> bool: """ @@ -108,10 +127,15 @@ def is_stable_version(version_str: str) -> bool: """ try: parsed_version = parse_version(version_str) - return isinstance(parsed_version, Version) and not parsed_version.is_prerelease and not parsed_version.is_devrelease + return ( + isinstance(parsed_version, Version) + and not parsed_version.is_prerelease + and not parsed_version.is_devrelease + ) except InvalidVersion: return False + # Function to extract the repository name from a Git remote URL def extract_repo_name(remote_url: str) -> str: """ @@ -123,14 +147,19 @@ def extract_repo_name(remote_url: str) -> str: Returns: str: The repository name. """ - match = re.search(r'[:/]([^/:]+/[^/\.]+)(\.git)?$', remote_url) + match = re.search(r"[:/]([^/:]+/[^/\.]+)(\.git)?$", remote_url) if match: return match.group(1) return remote_url # Function to wait for a list of CRDs to be present -def wait_for_crds(crd_names: List[str], k8s_provider: k8s.Provider, depends_on: List[pulumi.Resource], parent: pulumi.Resource) -> List[pulumi.Resource]: +def wait_for_crds( + crd_names: List[str], + k8s_provider: k8s.Provider, + depends_on: List[pulumi.Resource], + parent: pulumi.Resource, +) -> List[pulumi.Resource]: """ Waits for the specified CRDs to be present and ensures dependencies. @@ -168,9 +197,15 @@ def wait_for_crds(crd_names: List[str], k8s_provider: k8s.Provider, depends_on: return crds + # HACK: Create a dummy CRD definition to use during pulumi dry_run / preview runs if CRDs are not found. # TODO: Solve this in a more elegant way. -def create_dummy_crd(crd_name: str, k8s_provider: k8s.Provider, depends_on: List[pulumi.Resource], parent: pulumi.Resource) -> Optional[k8s.yaml.ConfigFile]: +def create_dummy_crd( + crd_name: str, + k8s_provider: k8s.Provider, + depends_on: List[pulumi.Resource], + parent: pulumi.Resource, +) -> Optional[k8s.yaml.ConfigFile]: """ Create a dummy CRD definition to use during preview runs. @@ -183,10 +218,10 @@ def create_dummy_crd(crd_name: str, k8s_provider: k8s.Provider, depends_on: List Returns: Optional[k8s.yaml.ConfigFile]: The dummy CRD resource. """ - parts = crd_name.split('.') + parts = crd_name.split(".") plural = parts[0] - group = '.'.join(parts[1:]) - kind = ''.join(word.title() for word in plural.split('_')) + group = ".".join(parts[1:]) + kind = "".join(word.title() for word in plural.split("_")) dummy_crd_yaml_template = """ apiVersion: apiextensions.k8s.io/v1 @@ -213,7 +248,7 @@ def create_dummy_crd(crd_name: str, k8s_provider: k8s.Provider, depends_on: List ) try: - with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp_file: + with tempfile.NamedTemporaryFile(delete=False, mode="w") as temp_file: temp_file.write(dummy_crd_yaml) temp_file_path = temp_file.name @@ -224,7 +259,7 @@ def create_dummy_crd(crd_name: str, k8s_provider: k8s.Provider, depends_on: List parent=parent, depends_on=depends_on, provider=k8s_provider, - ) + ), ) return dummy_crd finally: diff --git a/pulumi/modules/aws/config.py b/pulumi/modules/aws/config.py index 9ab7486..50ce57f 100644 --- a/pulumi/modules/aws/config.py +++ b/pulumi/modules/aws/config.py @@ -20,7 +20,11 @@ import pulumi from pulumi import log, Config, ResourceTransformationResult, ResourceTransformationArgs from pulumi_aws import Provider -from core.metadata import get_global_labels, generate_compliance_labels, generate_git_labels +from core.metadata import ( + get_global_labels, + generate_compliance_labels, + generate_git_labels, +) from core.types import ComplianceConfig from .types import AWSConfig, TenantAccountConfig from .taggable import TAGGABLE_RESOURCES @@ -29,6 +33,7 @@ MODULE_NAME = "aws" MODULE_VERSION = "0.0.1" + def initialize_aws_provider(config: AWSConfig) -> Provider: """ Initializes the AWS provider with the supplied configuration. @@ -40,9 +45,11 @@ def initialize_aws_provider(config: AWSConfig) -> Provider: Provider: An initialized AWS Provider for resource management. """ aws_config = pulumi.Config("aws") - aws_access_key = os.getenv('AWS_ACCESS_KEY_ID') or aws_config.get("access_key_id") - aws_secret_key = os.getenv('AWS_SECRET_ACCESS_KEY') or aws_config.get("secret_access_key") - profile = os.getenv('AWS_PROFILE') or config.profile + aws_access_key = os.getenv("AWS_ACCESS_KEY_ID") or aws_config.get("access_key_id") + aws_secret_key = os.getenv("AWS_SECRET_ACCESS_KEY") or aws_config.get( + "secret_access_key" + ) + profile = os.getenv("AWS_PROFILE") or config.profile return Provider( "awsProvider", @@ -52,6 +59,7 @@ def initialize_aws_provider(config: AWSConfig) -> Provider: region=config.region, ) + def generate_global_transformations(global_tags: Dict[str, str]) -> None: """ Registers a global transformation to apply tags to AWS resources that support tagging. @@ -59,7 +67,10 @@ def generate_global_transformations(global_tags: Dict[str, str]) -> None: Args: global_tags (Dict[str, str]): The global tags to apply. """ - def global_transform(args: ResourceTransformationArgs) -> Optional[ResourceTransformationResult]: + + def global_transform( + args: ResourceTransformationArgs, + ) -> Optional[ResourceTransformationResult]: resource_type = args.type_ # Check if the resource type is in the list of taggable resources @@ -68,9 +79,9 @@ def global_transform(args: ResourceTransformationArgs) -> Optional[ResourceTrans opts = args.opts # Merge existing tags with global tags - tags = props.get('tags') or {} + tags = props.get("tags") or {} tags.update(global_tags) - props['tags'] = tags + props["tags"] = tags return ResourceTransformationResult(props, opts) else: @@ -79,9 +90,13 @@ def global_transform(args: ResourceTransformationArgs) -> Optional[ResourceTrans pulumi.runtime.register_stack_transformation(global_transform) + # pulumi/modules/aws/config.py -def generate_tags(config: AWSConfig, compliance_config: ComplianceConfig, git_info: Dict[str, str]) -> Dict[str, str]: + +def generate_tags( + config: AWSConfig, compliance_config: ComplianceConfig, git_info: Dict[str, str] +) -> Dict[str, str]: """ Generates tags for AWS resources, including compliance and Git metadata. @@ -106,7 +121,7 @@ def generate_tags(config: AWSConfig, compliance_config: ComplianceConfig, git_in } # Log generated tags for visibility - #pulumi.log.info(f"Generated AWS tags: {json.dumps(aws_module_tags, indent=2)}") + # pulumi.log.info(f"Generated AWS tags: {json.dumps(aws_module_tags, indent=2)}") # Register the global transformation generate_global_transformations(aws_module_tags) @@ -125,11 +140,11 @@ def load_aws_config() -> AWSConfig: ValueError: If there's an issue with the AWS configuration format. """ config = Config() - aws_config_dict = config.get_object('aws') or {} - compliance_config_dict = config.get_object('compliance') or {} + aws_config_dict = config.get_object("aws") or {} + compliance_config_dict = config.get_object("compliance") or {} # Include the compliance config into the aws_config_dict - aws_config_dict['compliance'] = compliance_config_dict + aws_config_dict["compliance"] = compliance_config_dict try: aws_config = AWSConfig.merge(aws_config_dict) @@ -148,8 +163,8 @@ def load_tenant_account_configs() -> Dict[str, TenantAccountConfig]: Dict[str, TenantAccountConfig]: Configurations for each tenant account. """ config = Config() - aws_config_dict = config.get_object('aws') or {} - tenant_accounts_list = aws_config_dict.get('landingzones', []) + aws_config_dict = config.get_object("aws") or {} + tenant_accounts_list = aws_config_dict.get("landingzones", []) tenant_accounts = {} for tenant in tenant_accounts_list: @@ -157,6 +172,8 @@ def load_tenant_account_configs() -> Dict[str, TenantAccountConfig]: tenant_config = TenantAccountConfig(**tenant) tenant_accounts[tenant_config.name] = tenant_config except Exception as e: - log.warn(f"Invalid tenant account configuration for '{tenant.get('name', 'unknown')}': {e}") + log.warn( + f"Invalid tenant account configuration for '{tenant.get('name', 'unknown')}': {e}" + ) return tenant_accounts diff --git a/pulumi/modules/aws/deploy.py b/pulumi/modules/aws/deploy.py index f32382d..c681880 100644 --- a/pulumi/modules/aws/deploy.py +++ b/pulumi/modules/aws/deploy.py @@ -33,7 +33,9 @@ MODULE_VERSION = "0.0.1" -def deploy_aws_module(config: AWSConfig, global_depends_on: List[pulumi.Resource]) -> Tuple[str, pulumi.Resource]: +def deploy_aws_module( + config: AWSConfig, global_depends_on: List[pulumi.Resource] +) -> Tuple[str, pulumi.Resource]: """ Deploys the AWS module resources. @@ -75,7 +77,7 @@ def deploy_aws_module(config: AWSConfig, global_depends_on: List[pulumi.Resource organization, root_id, ["SecOps", "Infrastructure", "Applications"], - aws_provider + aws_provider, ) # Create Tenant Accounts under 'Applications' OU @@ -83,10 +85,7 @@ def deploy_aws_module(config: AWSConfig, global_depends_on: List[pulumi.Resource if ou_applications: tenant_configs = load_tenant_account_configs() tenant_accounts = create_tenant_accounts( - organization, - ou_applications, - tenant_configs, - aws_provider + organization, ou_applications, tenant_configs, aws_provider ) # Deploy resources for each tenant @@ -95,15 +94,13 @@ def deploy_aws_module(config: AWSConfig, global_depends_on: List[pulumi.Resource tenant_account=tenant_account, role_name="OrganizationAccountAccessRole", region=config.region, - aws_provider=aws_provider + aws_provider=aws_provider, ) tenant_config = tenant_configs.get(tenant_account.name) if tenant_config: deploy_tenant_resources( - tenant_provider, - tenant_account, - tenant_config + tenant_provider, tenant_account, tenant_config ) # Return Dictionary of AWS Module Resources to global configuration dictionary @@ -111,7 +108,7 @@ def deploy_aws_module(config: AWSConfig, global_depends_on: List[pulumi.Resource "ops_data_bucket": s3_bucket.id, "organization": organization.id, "organization_arn": organization.arn, - "aws_module_tags": module_tags + "aws_module_tags": module_tags, } # Return the module version and the main resource diff --git a/pulumi/modules/aws/eks_donor_template.py b/pulumi/modules/aws/eks_donor_template.py index c9d6a4a..3f2e7da 100644 --- a/pulumi/modules/aws/eks_donor_template.py +++ b/pulumi/modules/aws/eks_donor_template.py @@ -15,7 +15,9 @@ vpc_cidr = config.get("vpc_cidr") or "10.0.0.0/16" # Allowed SSH CIDR (replace with your IP range) -allowed_ssh_cidr = config.get("allowed_ssh_cidr") or "203.0.113.0/24" # Example IP range +allowed_ssh_cidr = ( + config.get("allowed_ssh_cidr") or "203.0.113.0/24" +) # Example IP range # EKS Cluster Configuration eks_cluster_name = config.get("eks_cluster_name") or f"eks-cluster-{stack_name}" @@ -34,67 +36,71 @@ azs = aws.get_availability_zones(state="available").names[:3] # Create IAM role for EKS Cluster -eks_role = aws_native.iam.Role(f"eksRole-{stack_name}", - assume_role_policy_document=json.dumps({ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": {"Service": "eks.amazonaws.com"}, - "Action": "sts:AssumeRole" - }] - }), +eks_role = aws_native.iam.Role( + f"eksRole-{stack_name}", + assume_role_policy_document=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "eks.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + ), managed_policy_arns=[ "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy", - "arn:aws:iam::aws:policy/AmazonEKSServicePolicy" + "arn:aws:iam::aws:policy/AmazonEKSServicePolicy", ], role_name=eks_role_name, ) # Create IAM role for EKS Node Group -node_role = aws_native.iam.Role(f"nodeRole-{stack_name}", - assume_role_policy_document=json.dumps({ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": {"Service": "ec2.amazonaws.com"}, - "Action": "sts:AssumeRole" - }] - }), +node_role = aws_native.iam.Role( + f"nodeRole-{stack_name}", + assume_role_policy_document=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "ec2.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + ), managed_policy_arns=[ "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy", "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", - "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly", ], role_name=node_role_name, ) # Create VPC -vpc = aws_native.ec2.VPC(f"vpc-{stack_name}", +vpc = aws_native.ec2.VPC( + f"vpc-{stack_name}", cidr_block=vpc_cidr, enable_dns_support=True, enable_dns_hostnames=True, - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"vpc-{stack_name}" - )] + tags=[aws_native.ec2.TagArgs(key="Name", value=f"vpc-{stack_name}")], ) # Create Internet Gateway -igw = aws_native.ec2.InternetGateway(f"igw-{stack_name}", +igw = aws_native.ec2.InternetGateway( + f"igw-{stack_name}", vpc_id=vpc.id, - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"igw-{stack_name}" - )] + tags=[aws_native.ec2.TagArgs(key="Name", value=f"igw-{stack_name}")], ) # Create Elastic IP for NAT Gateway -nat_eip = aws_native.ec2.EIP(f"natEip-{stack_name}", +nat_eip = aws_native.ec2.EIP( + f"natEip-{stack_name}", domain="vpc", - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"natEip-{stack_name}" - )] + tags=[aws_native.ec2.TagArgs(key="Name", value=f"natEip-{stack_name}")], ) # Create Subnets across multiple AZs @@ -102,65 +108,61 @@ private_subnets = [] for i, az in enumerate(azs): - public_subnet = aws_native.ec2.Subnet(f"publicSubnet-{stack_name}-{i}", + public_subnet = aws_native.ec2.Subnet( + f"publicSubnet-{stack_name}-{i}", vpc_id=vpc.id, cidr_block=f"10.0.{i}.0/24", map_public_ip_on_launch=True, availability_zone=az, - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"publicSubnet-{stack_name}-{i}" - )] + tags=[ + aws_native.ec2.TagArgs(key="Name", value=f"publicSubnet-{stack_name}-{i}") + ], ) public_subnets.append(public_subnet) - private_subnet = aws_native.ec2.Subnet(f"privateSubnet-{stack_name}-{i}", + private_subnet = aws_native.ec2.Subnet( + f"privateSubnet-{stack_name}-{i}", vpc_id=vpc.id, cidr_block=f"10.0.{i+10}.0/24", map_public_ip_on_launch=False, availability_zone=az, - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"privateSubnet-{stack_name}-{i}" - )] + tags=[ + aws_native.ec2.TagArgs(key="Name", value=f"privateSubnet-{stack_name}-{i}") + ], ) private_subnets.append(private_subnet) # Create NAT Gateway -nat_gateway = aws_native.ec2.NatGateway(f"natGateway-{stack_name}", +nat_gateway = aws_native.ec2.NatGateway( + f"natGateway-{stack_name}", subnet_id=public_subnets[0].id, allocation_id=nat_eip.allocation_id, - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"natGateway-{stack_name}" - )] + tags=[aws_native.ec2.TagArgs(key="Name", value=f"natGateway-{stack_name}")], ) # Create Route Tables -public_route_table = aws_native.ec2.RouteTable(f"publicRouteTable-{stack_name}", +public_route_table = aws_native.ec2.RouteTable( + f"publicRouteTable-{stack_name}", vpc_id=vpc.id, - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"publicRouteTable-{stack_name}" - )] + tags=[aws_native.ec2.TagArgs(key="Name", value=f"publicRouteTable-{stack_name}")], ) -private_route_table = aws_native.ec2.RouteTable(f"privateRouteTable-{stack_name}", +private_route_table = aws_native.ec2.RouteTable( + f"privateRouteTable-{stack_name}", vpc_id=vpc.id, - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"privateRouteTable-{stack_name}" - )] + tags=[aws_native.ec2.TagArgs(key="Name", value=f"privateRouteTable-{stack_name}")], ) # Create Routes -public_route = aws_native.ec2.Route(f"publicRoute-{stack_name}", +public_route = aws_native.ec2.Route( + f"publicRoute-{stack_name}", route_table_id=public_route_table.id, destination_cidr_block="0.0.0.0/0", gateway_id=igw.id, ) -private_route = aws_native.ec2.Route(f"privateRoute-{stack_name}", +private_route = aws_native.ec2.Route( + f"privateRoute-{stack_name}", route_table_id=private_route_table.id, destination_cidr_block="0.0.0.0/0", nat_gateway_id=nat_gateway.id, @@ -168,110 +170,121 @@ # Associate Subnets with Route Tables for i, subnet in enumerate(public_subnets): - aws_native.ec2.SubnetRouteTableAssociation(f"publicSubnetRouteTableAssociation-{stack_name}-{i}", + aws_native.ec2.SubnetRouteTableAssociation( + f"publicSubnetRouteTableAssociation-{stack_name}-{i}", subnet_id=subnet.id, - route_table_id=public_route_table.id + route_table_id=public_route_table.id, ) for i, subnet in enumerate(private_subnets): - aws_native.ec2.SubnetRouteTableAssociation(f"privateSubnetRouteTableAssociation-{stack_name}-{i}", + aws_native.ec2.SubnetRouteTableAssociation( + f"privateSubnetRouteTableAssociation-{stack_name}-{i}", subnet_id=subnet.id, - route_table_id=private_route_table.id + route_table_id=private_route_table.id, ) # Create Security Groups -cluster_security_group = aws_native.ec2.SecurityGroup(f"clusterSecurityGroup-{stack_name}", +cluster_security_group = aws_native.ec2.SecurityGroup( + f"clusterSecurityGroup-{stack_name}", vpc_id=vpc.id, group_description="EKS cluster security group", - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"clusterSecurityGroup-{stack_name}" - )] + tags=[ + aws_native.ec2.TagArgs(key="Name", value=f"clusterSecurityGroup-{stack_name}") + ], ) -node_security_group = aws_native.ec2.SecurityGroup(f"nodeSecurityGroup-{stack_name}", +node_security_group = aws_native.ec2.SecurityGroup( + f"nodeSecurityGroup-{stack_name}", vpc_id=vpc.id, group_description="EKS node group security group", - tags=[aws_native.ec2.TagArgs( - key="Name", - value=f"nodeSecurityGroup-{stack_name}" - )] + tags=[aws_native.ec2.TagArgs(key="Name", value=f"nodeSecurityGroup-{stack_name}")], ) # Security Group Rules -aws_native.ec2.SecurityGroupIngress(f"nodeToClusterAPIServer-{stack_name}", +aws_native.ec2.SecurityGroupIngress( + f"nodeToClusterAPIServer-{stack_name}", group_id=cluster_security_group.id, ip_protocol="tcp", from_port=443, to_port=443, - source_security_group_id=node_security_group.id + source_security_group_id=node_security_group.id, ) -aws_native.ec2.SecurityGroupEgress(f"clusterToNodeGroup-{stack_name}", +aws_native.ec2.SecurityGroupEgress( + f"clusterToNodeGroup-{stack_name}", group_id=cluster_security_group.id, ip_protocol="tcp", from_port=1025, to_port=65535, - destination_security_group_id=node_security_group.id + destination_security_group_id=node_security_group.id, ) -aws_native.ec2.SecurityGroupIngress(f"clusterToNodeGroupIngress-{stack_name}", +aws_native.ec2.SecurityGroupIngress( + f"clusterToNodeGroupIngress-{stack_name}", group_id=node_security_group.id, ip_protocol="-1", from_port=0, to_port=0, - source_security_group_id=cluster_security_group.id + source_security_group_id=cluster_security_group.id, ) -aws_native.ec2.SecurityGroupIngress(f"nodeToNodeIngress-{stack_name}", +aws_native.ec2.SecurityGroupIngress( + f"nodeToNodeIngress-{stack_name}", group_id=node_security_group.id, ip_protocol="-1", from_port=0, to_port=0, - source_security_group_id=node_security_group.id + source_security_group_id=node_security_group.id, ) # Allow SSH access to nodes (restricted CIDR) -aws_native.ec2.SecurityGroupIngress(f"sshAccess-{stack_name}", +aws_native.ec2.SecurityGroupIngress( + f"sshAccess-{stack_name}", group_id=node_security_group.id, ip_protocol="tcp", from_port=22, to_port=22, - cidr_ip=allowed_ssh_cidr + cidr_ip=allowed_ssh_cidr, ) # Create KMS Key for EKS Secrets Encryption -kms_key = aws_native.kms.Key(f"kmsKey-{stack_name}", +kms_key = aws_native.kms.Key( + f"kmsKey-{stack_name}", description="KMS key for EKS secrets encryption", - enable_key_rotation=True + enable_key_rotation=True, ) # Create EKS Cluster with Secrets Encryption and Private Endpoint -eks_cluster = aws_native.eks.Cluster(f"eksCluster-{stack_name}", +eks_cluster = aws_native.eks.Cluster( + f"eksCluster-{stack_name}", name=eks_cluster_name, role_arn=eks_role.arn, version="1.26", - encryption_config=[aws_native.eks.ClusterEncryptionConfigArgs( - provider=aws_native.eks.ProviderArgs( - key_arn=kms_key.key_arn - ), - resources=["secrets"] - )], + encryption_config=[ + aws_native.eks.ClusterEncryptionConfigArgs( + provider=aws_native.eks.ProviderArgs(key_arn=kms_key.key_arn), + resources=["secrets"], + ) + ], resources_vpc_config=aws_native.eks.ClusterResourcesVpcConfigArgs( subnet_ids=[subnet.id for subnet in private_subnets], security_group_ids=[cluster_security_group.id], endpoint_public_access=False, - endpoint_private_access=True + endpoint_private_access=True, ), - enabled_cluster_log_types=["api", "audit", "authenticator", "controllerManager", "scheduler"], - tags=[aws_native.eks.ClusterTagArgs( - key="Name", - value=eks_cluster_name - )] + enabled_cluster_log_types=[ + "api", + "audit", + "authenticator", + "controllerManager", + "scheduler", + ], + tags=[aws_native.eks.ClusterTagArgs(key="Name", value=eks_cluster_name)], ) # Create EKS Node Group -node_group = aws_native.eks.Nodegroup(f"nodeGroup-{stack_name}", +node_group = aws_native.eks.Nodegroup( + f"nodeGroup-{stack_name}", cluster_name=eks_cluster.name, node_role=node_role.arn, subnets=[subnet.id for subnet in private_subnets], @@ -286,113 +299,121 @@ ec2_ssh_key=ssh_key_name, source_security_groups=[node_security_group.id], ), - tags=[aws_native.eks.NodegroupTagArgs( - key="Name", - value=nodegroup_name - )] + tags=[aws_native.eks.NodegroupTagArgs(key="Name", value=nodegroup_name)], ) # Create VPC Flow Logs -flow_logs_role = aws_native.iam.Role(f"vpcFlowLogsRole-{stack_name}", - assume_role_policy_document=json.dumps({ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": {"Service": "vpc-flow-logs.amazonaws.com"}, - "Action": "sts:AssumeRole" - }] - }), +flow_logs_role = aws_native.iam.Role( + f"vpcFlowLogsRole-{stack_name}", + assume_role_policy_document=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "vpc-flow-logs.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + ), managed_policy_arns=[ "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" ], ) -flow_log_group = aws_native.logs.LogGroup(f"vpcFlowLogGroup-{stack_name}", +flow_log_group = aws_native.logs.LogGroup( + f"vpcFlowLogGroup-{stack_name}", log_group_name=f"/aws/vpc/flowlogs/{vpc.id}", retention_in_days=90, - tags=[aws_native.logs.LogGroupTagArgs( - key="Name", - value=f"vpcFlowLogGroup-{stack_name}" - )] + tags=[ + aws_native.logs.LogGroupTagArgs( + key="Name", value=f"vpcFlowLogGroup-{stack_name}" + ) + ], ) -vpc_flow_log = aws_native.ec2.FlowLog(f"vpcFlowLog-{stack_name}", +vpc_flow_log = aws_native.ec2.FlowLog( + f"vpcFlowLog-{stack_name}", resource_id=vpc.id, resource_type="VPC", traffic_type="ALL", log_destination_type="cloud-watch-logs", log_group_name=flow_log_group.name, - deliver_logs_permission_arn=flow_logs_role.arn + deliver_logs_permission_arn=flow_logs_role.arn, ) + # Generate Kubeconfig def generate_kubeconfig(cluster_name, cluster_endpoint, cluster_certificate_authority): kubeconfig = { "apiVersion": "v1", - "clusters": [{ - "cluster": { - "server": cluster_endpoint, - "certificate-authority-data": cluster_certificate_authority, - }, - "name": "kubernetes", - }], - "contexts": [{ - "context": { - "cluster": "kubernetes", - "user": "aws", - }, - "name": "aws", - }], + "clusters": [ + { + "cluster": { + "server": cluster_endpoint, + "certificate-authority-data": cluster_certificate_authority, + }, + "name": "kubernetes", + } + ], + "contexts": [ + { + "context": { + "cluster": "kubernetes", + "user": "aws", + }, + "name": "aws", + } + ], "current-context": "aws", "kind": "Config", "preferences": {}, - "users": [{ - "name": "aws", - "user": { - "exec": { - "apiVersion": "client.authentication.k8s.io/v1beta1", - "command": "aws", - "args": ["eks", "get-token", "--cluster-name", cluster_name], - } + "users": [ + { + "name": "aws", + "user": { + "exec": { + "apiVersion": "client.authentication.k8s.io/v1beta1", + "command": "aws", + "args": ["eks", "get-token", "--cluster-name", cluster_name], + } + }, } - }] + ], } return kubeconfig + # Export Kubeconfig kubeconfig = pulumi.Output.all( - eks_cluster.name, - eks_cluster.endpoint, - eks_cluster.certificate_authority -).apply(lambda args: generate_kubeconfig( - args[0], - args[1], - base64.b64decode(args[2]['data']).decode('utf-8') -)) + eks_cluster.name, eks_cluster.endpoint, eks_cluster.certificate_authority +).apply( + lambda args: generate_kubeconfig( + args[0], args[1], base64.b64decode(args[2]["data"]).decode("utf-8") + ) +) # Create Kubernetes Provider -k8s_provider = k8s.Provider(f"k8sProvider-{stack_name}", - kubeconfig=kubeconfig -) +k8s_provider = k8s.Provider(f"k8sProvider-{stack_name}", kubeconfig=kubeconfig) # Deploy Fluent Bit DaemonSet for Application Logging fluent_bit_manifest = { "apiVersion": "v1", "kind": "Namespace", - "metadata": {"name": "logging"} + "metadata": {"name": "logging"}, } -fluent_bit_namespace = k8s.core.v1.Namespace("fluent-bit-namespace", +fluent_bit_namespace = k8s.core.v1.Namespace( + "fluent-bit-namespace", metadata=fluent_bit_manifest["metadata"], - opts=pulumi.ResourceOptions(provider=k8s_provider) + opts=pulumi.ResourceOptions(provider=k8s_provider), ) # Fluent Bit DaemonSet and ConfigMap -fluent_bit_config_map = k8s.core.v1.ConfigMap("fluent-bit-config", - metadata={ - "name": "fluent-bit-config", - "namespace": "logging" - }, +fluent_bit_config_map = k8s.core.v1.ConfigMap( + "fluent-bit-config", + metadata={"name": "fluent-bit-config", "namespace": "logging"}, data={ "fluent-bit.conf": """ [SERVICE] @@ -418,7 +439,8 @@ def generate_kubeconfig(cluster_name, cluster_endpoint, cluster_certificate_auth log_group_name fluent-bit-cloudwatch log_stream_prefix from-fluent-bit- auto_create_group true -""" % aws.config.region, +""" + % aws.config.region, "parsers.conf": """ [PARSER] Name docker @@ -426,49 +448,48 @@ def generate_kubeconfig(cluster_name, cluster_endpoint, cluster_certificate_auth Time_Key time Time_Format %Y-%m-%dT%H:%M:%S.%L Time_Keep On -""" +""", }, - opts=pulumi.ResourceOptions(provider=k8s_provider) + opts=pulumi.ResourceOptions(provider=k8s_provider), ) -fluent_bit_service_account = k8s.core.v1.ServiceAccount("fluent-bit-sa", - metadata={ - "name": "fluent-bit", - "namespace": "logging" - }, - opts=pulumi.ResourceOptions(provider=k8s_provider) +fluent_bit_service_account = k8s.core.v1.ServiceAccount( + "fluent-bit-sa", + metadata={"name": "fluent-bit", "namespace": "logging"}, + opts=pulumi.ResourceOptions(provider=k8s_provider), ) -fluent_bit_cluster_role = k8s.rbac.v1.ClusterRole("fluent-bit-cluster-role", +fluent_bit_cluster_role = k8s.rbac.v1.ClusterRole( + "fluent-bit-cluster-role", metadata={"name": "fluent-bit-read"}, - rules=[{ - "apiGroups": [""], - "resources": ["namespaces", "pods"], - "verbs": ["get", "list", "watch"] - }], - opts=pulumi.ResourceOptions(provider=k8s_provider) + rules=[ + { + "apiGroups": [""], + "resources": ["namespaces", "pods"], + "verbs": ["get", "list", "watch"], + } + ], + opts=pulumi.ResourceOptions(provider=k8s_provider), ) -fluent_bit_cluster_role_binding = k8s.rbac.v1.ClusterRoleBinding("fluent-bit-cluster-role-binding", +fluent_bit_cluster_role_binding = k8s.rbac.v1.ClusterRoleBinding( + "fluent-bit-cluster-role-binding", metadata={"name": "fluent-bit-read"}, - subjects=[{ - "kind": "ServiceAccount", - "name": "fluent-bit", - "namespace": "logging" - }], + subjects=[{"kind": "ServiceAccount", "name": "fluent-bit", "namespace": "logging"}], role_ref={ "kind": "ClusterRole", "name": "fluent-bit-read", - "apiGroup": "rbac.authorization.k8s.io" + "apiGroup": "rbac.authorization.k8s.io", }, - opts=pulumi.ResourceOptions(provider=k8s_provider) + opts=pulumi.ResourceOptions(provider=k8s_provider), ) -fluent_bit_daemonset = k8s.apps.v1.DaemonSet("fluent-bit-daemonset", +fluent_bit_daemonset = k8s.apps.v1.DaemonSet( + "fluent-bit-daemonset", metadata={ "name": "fluent-bit", "namespace": "logging", - "labels": {"k8s-app": "fluent-bit-logging"} + "labels": {"k8s-app": "fluent-bit-logging"}, }, spec={ "selector": {"matchLabels": {"k8s-app": "fluent-bit-logging"}}, @@ -477,29 +498,46 @@ def generate_kubeconfig(cluster_name, cluster_endpoint, cluster_certificate_auth "spec": { "serviceAccountName": "fluent-bit", "tolerations": [{"operator": "Exists"}], - "containers": [{ - "name": "fluent-bit", - "image": "amazon/aws-for-fluent-bit:latest", - "resources": { - "limits": {"memory": "200Mi"}, - "requests": {"cpu": "100m", "memory": "100Mi"} - }, - "volumeMounts": [ - {"name": "varlog", "mountPath": "/var/log"}, - {"name": "varlibdockercontainers", "mountPath": "/var/lib/docker/containers", "readOnly": True}, - {"name": "config", "mountPath": "/fluent-bit/etc/fluent-bit.conf", "subPath": "fluent-bit.conf"}, - {"name": "config", "mountPath": "/fluent-bit/etc/parsers.conf", "subPath": "parsers.conf"}, - ] - }], + "containers": [ + { + "name": "fluent-bit", + "image": "amazon/aws-for-fluent-bit:latest", + "resources": { + "limits": {"memory": "200Mi"}, + "requests": {"cpu": "100m", "memory": "100Mi"}, + }, + "volumeMounts": [ + {"name": "varlog", "mountPath": "/var/log"}, + { + "name": "varlibdockercontainers", + "mountPath": "/var/lib/docker/containers", + "readOnly": True, + }, + { + "name": "config", + "mountPath": "/fluent-bit/etc/fluent-bit.conf", + "subPath": "fluent-bit.conf", + }, + { + "name": "config", + "mountPath": "/fluent-bit/etc/parsers.conf", + "subPath": "parsers.conf", + }, + ], + } + ], "volumes": [ {"name": "varlog", "hostPath": {"path": "/var/log"}}, - {"name": "varlibdockercontainers", "hostPath": {"path": "/var/lib/docker/containers"}}, - {"name": "config", "configMap": {"name": "fluent-bit-config"}} - ] - } - } + { + "name": "varlibdockercontainers", + "hostPath": {"path": "/var/lib/docker/containers"}, + }, + {"name": "config", "configMap": {"name": "fluent-bit-config"}}, + ], + }, + }, }, - opts=pulumi.ResourceOptions(provider=k8s_provider) + opts=pulumi.ResourceOptions(provider=k8s_provider), ) # Export outputs diff --git a/pulumi/modules/aws/resources.py b/pulumi/modules/aws/resources.py index 38e3a06..ef1251d 100644 --- a/pulumi/modules/aws/resources.py +++ b/pulumi/modules/aws/resources.py @@ -31,28 +31,38 @@ AWSConfig, GlobalTags, ) -from core.metadata import set_global_labels, set_global_annotations, generate_compliance_labels +from core.metadata import ( + set_global_labels, + set_global_annotations, + generate_compliance_labels, +) from core.utils import set_resource_metadata + def fetch_sts_identity(aws_provider: aws.Provider) -> pulumi.Output[Dict[str, str]]: try: - identity = aws.get_caller_identity(opts=pulumi.InvokeOptions(provider=aws_provider)) + identity = aws.get_caller_identity( + opts=pulumi.InvokeOptions(provider=aws_provider) + ) # Map the output to a dictionary with explicit keys - return pulumi.Output.from_input({ - "account_id": identity.account_id, - "arn": identity.arn, - "id": identity.id, - "user_id": identity.user_id - }) + return pulumi.Output.from_input( + { + "account_id": identity.account_id, + "arn": identity.arn, + "id": identity.id, + "user_id": identity.user_id, + } + ) except Exception as e: log.error(f"Error fetching STS Caller Identity: {str(e)}") raise + def create_s3_bucket( - bucket_name: str, - aws_provider: aws.Provider, - ) -> aws.s3.Bucket: + bucket_name: str, + aws_provider: aws.Provider, +) -> aws.s3.Bucket: """ Creates an S3 Bucket with the specified tags. @@ -66,21 +76,24 @@ def create_s3_bucket( aws.s3.Bucket: The created S3 bucket. """ # Compliance labels now part of global transformations; ensure bucket tags are included. - bucket = aws.s3.Bucket( - bucket_name, - opts=ResourceOptions(provider=aws_provider) - ) + bucket = aws.s3.Bucket(bucket_name, opts=ResourceOptions(provider=aws_provider)) return bucket + def get_organization_details(provider: aws.Provider): try: - return aws.organizations.get_organization(opts=pulumi.InvokeOptions(provider=aws_provider)) + return aws.organizations.get_organization( + opts=pulumi.InvokeOptions(provider=aws_provider) + ) except Exception as e: log.warn(f"Failed to get existing organization: {str(e)}") return None -def setup_organization_units(org_details, config: AWSConfig, tags: dict, aws_provider: aws.Provider): + +def setup_organization_units( + org_details, config: AWSConfig, tags: dict, aws_provider: aws.Provider +): if org_details.roots: root_id = org_details.roots[0].id if config.control_tower.enabled: @@ -88,11 +101,12 @@ def setup_organization_units(org_details, config: AWSConfig, tags: dict, aws_pro "example-ou", name="example-ou", parent_id=root_id, - opts=pulumi.ResourceOptions(provider=aws_provider) + opts=pulumi.ResourceOptions(provider=aws_provider), ) else: log.warn("No roots found in the organization.") + def create_organization(aws_provider: aws.Provider) -> aws.organizations.Organization: """ Creates an AWS Organization with all features enabled. @@ -111,7 +125,9 @@ def create_organization(aws_provider: aws.Provider) -> aws.organizations.Organiz ) # Use .apply to log the organization ID - organization.id.apply(lambda org_id: log.info(f"Organization created with ID: {org_id}")) + organization.id.apply( + lambda org_id: log.info(f"Organization created with ID: {org_id}") + ) return organization @@ -119,7 +135,10 @@ def create_organization(aws_provider: aws.Provider) -> aws.organizations.Organiz log.error(f"Failed to create organization: {str(e)}") raise -def get_organization_root_id(organization_data: aws.organizations.GetOrganizationResult) -> str: + +def get_organization_root_id( + organization_data: aws.organizations.GetOrganizationResult, +) -> str: """ Retrieves the root ID of the AWS Organization from the organization data. @@ -142,9 +161,10 @@ def get_organization_root_id(organization_data: aws.organizations.GetOrganizatio log.error(f"Error fetching organization roots: {str(e)}") raise + def get_or_create_organization( - aws_provider: aws.Provider, - ) -> Tuple[aws.organizations.Organization, aws.organizations.GetOrganizationResult]: + aws_provider: aws.Provider, +) -> Tuple[aws.organizations.Organization, aws.organizations.GetOrganizationResult]: """ Retrieves the existing AWS Organization or creates a new one if it doesn't exist. @@ -162,7 +182,7 @@ def get_or_create_organization( organization = aws.organizations.Organization.get( resource_name="existing_organization", id=organization_data.id, - opts=pulumi.ResourceOptions(provider=aws_provider) + opts=pulumi.ResourceOptions(provider=aws_provider), ) return organization, organization_data @@ -173,11 +193,12 @@ def get_or_create_organization( # return organization raise Exception("Unable to retrieve or create the AWS Organization") + def create_organizational_units( organization: aws.organizations.Organization, root_id: str, ou_names: List[str], - aws_provider: aws.Provider + aws_provider: aws.Provider, ) -> Dict[str, aws.organizations.OrganizationalUnit]: """ Creates Organizational Units (OUs) under the specified AWS Organization. @@ -199,7 +220,7 @@ def create_organizational_units( resource_name=f"ou_{ou_name.lower()}", name=ou_name, parent_id=root_id, - opts=ResourceOptions(provider=aws_provider, parent=organization) + opts=ResourceOptions(provider=aws_provider, parent=organization), ) ou_map[ou_name] = ou else: @@ -207,6 +228,7 @@ def create_organizational_units( return ou_map + def setup_control_tower(control_tower_config: ControlTowerConfig) -> None: """ Sets up AWS Control Tower based on the provided configuration. @@ -217,15 +239,16 @@ def setup_control_tower(control_tower_config: ControlTowerConfig) -> None: if control_tower_config.enabled: # Placeholder for Control Tower setup logic # AWS Control Tower does not currently support full automation via API/IaC - log.info("AWS Control Tower setup is enabled. Manual configuration may be required.") + log.info( + "AWS Control Tower setup is enabled. Manual configuration may be required." + ) else: log.info("AWS Control Tower setup is disabled.") + def create_iam_users( - iam_users: List[IAMUserConfig], - tags: Dict[str, str], - aws_provider: aws.Provider - ) -> None: + iam_users: List[IAMUserConfig], tags: Dict[str, str], aws_provider: aws.Provider +) -> None: """ Creates IAM users and associates them with groups and policies. @@ -239,7 +262,7 @@ def create_iam_users( name=user_config.name, opts=pulumi.ResourceOptions( provider=aws_provider, - ) + ), ) for group_name in user_config.groups: @@ -248,7 +271,7 @@ def create_iam_users( name=group_name, opts=pulumi.ResourceOptions( provider=aws_provider, - ) + ), ) aws.iam.UserGroupMembership( resource_name=f"{user_config.name}_{group_name}_membership", @@ -256,7 +279,7 @@ def create_iam_users( groups=[iam_group.name], opts=pulumi.ResourceOptions( provider=aws_provider, - ) + ), ) for policy_arn in user_config.policies: @@ -266,9 +289,10 @@ def create_iam_users( policy_arn=policy_arn, opts=pulumi.ResourceOptions( provider=aws_provider, - ) + ), ) + def create_tenant_accounts( organization: aws.organizations.Organization, ou: aws.organizations.OrganizationalUnit, @@ -305,11 +329,12 @@ def create_tenant_accounts( return tenant_accounts + def assume_role_in_tenant_account( tenant_account: aws.organizations.Account, role_name: str, region: str, - aws_provider: aws.Provider + aws_provider: aws.Provider, ) -> aws.Provider: """ Assumes a role in the tenant account to perform operations. @@ -323,8 +348,8 @@ def assume_role_in_tenant_account( Returns: aws.Provider: AWS provider configured for the tenant account. """ - return tenant_account.id.apply(lambda account_id: - aws.Provider( + return tenant_account.id.apply( + lambda account_id: aws.Provider( f"tenant_provider_{account_id}", assume_role=aws.ProviderAssumeRoleArgs( role_arn=f"arn:aws:iam::{account_id}:role/{role_name}", @@ -334,10 +359,11 @@ def assume_role_in_tenant_account( ) ) + def deploy_tenant_resources( tenant_provider: aws.Provider, tenant_account: aws.organizations.Account, - tenant_config: TenantAccountConfig + tenant_config: TenantAccountConfig, ) -> None: """ Deploys resources in the tenant account based on the configuration. @@ -348,10 +374,12 @@ def deploy_tenant_resources( tenant_config: Configuration for the tenant account. """ if not tenant_config: - log.warn(f"Configuration for tenant account '{tenant_account.name}' is missing.") + log.warn( + f"Configuration for tenant account '{tenant_account.name}' is missing." + ) return - if 'bucket' in tenant_config.features: + if "bucket" in tenant_config.features: bucket = aws.s3.Bucket( resource_name=f"{tenant_account.name}_bucket", bucket=tenant_account.name.apply(lambda x: f"{x}-bucket"), @@ -360,7 +388,7 @@ def deploy_tenant_resources( ) pulumi.export(f"{tenant_account.name}_bucket_name", bucket.bucket) - if 'ec2' in tenant_config.features: + if "ec2" in tenant_config.features: ec2_instance = aws.ec2.Instance( resource_name=f"{tenant_account.name}_instance", ami="ami-0c94855ba95c71c99", @@ -369,6 +397,7 @@ def deploy_tenant_resources( ) pulumi.export(f"{tenant_account.name}_instance_id", ec2_instance.id) + def create_vpc( vpc_name: str, cidr_block: str, @@ -398,6 +427,7 @@ def create_vpc( ) return vpc + def create_subnet( subnet_name: str, cidr_block: str, @@ -428,6 +458,7 @@ def create_subnet( ) return subnet + def create_security_group( sg_name: str, vpc_id: str, @@ -481,6 +512,7 @@ def create_security_group( ) return sg + def create_internet_gateway( igw_name: str, vpc_id: str, @@ -508,6 +540,7 @@ def create_internet_gateway( ) return igw + def create_route_table( rt_name: str, vpc_id: str, @@ -535,6 +568,7 @@ def create_route_table( ) return rt + def create_route( route_name: str, route_table_id: str, @@ -568,6 +602,7 @@ def create_route( ) return route + def create_subnet_route_table_association( association_name: str, subnet_id: str, @@ -598,6 +633,7 @@ def create_subnet_route_table_association( ) return association + def create_security_group_rule( rule_name: str, security_group_id: str, @@ -640,6 +676,7 @@ def create_security_group_rule( ) return rule + def create_ec2_instance( instance_name: str, ami: str, diff --git a/pulumi/modules/aws/taggable.py b/pulumi/modules/aws/taggable.py index bf0b2d6..d124f33 100644 --- a/pulumi/modules/aws/taggable.py +++ b/pulumi/modules/aws/taggable.py @@ -2,700 +2,700 @@ # Do not edit manually. TAGGABLE_RESOURCES = [ - 'aws:accessanalyzer/analyzer:Analyzer', - 'aws:acm/certificate:Certificate', - 'aws:acmpca/certificateAuthority:CertificateAuthority', - 'aws:alb/listener:Listener', - 'aws:alb/listenerRule:ListenerRule', - 'aws:alb/loadBalancer:LoadBalancer', - 'aws:alb/targetGroup:TargetGroup', - 'aws:amp/scraper:Scraper', - 'aws:amp/workspace:Workspace', - 'aws:amplify/app:App', - 'aws:amplify/branch:Branch', - 'aws:apigateway/apiKey:ApiKey', - 'aws:apigateway/clientCertificate:ClientCertificate', - 'aws:apigateway/domainName:DomainName', - 'aws:apigateway/restApi:RestApi', - 'aws:apigateway/stage:Stage', - 'aws:apigateway/usagePlan:UsagePlan', - 'aws:apigateway/vpcLink:VpcLink', - 'aws:apigatewayv2/api:Api', - 'aws:apigatewayv2/domainName:DomainName', - 'aws:apigatewayv2/stage:Stage', - 'aws:apigatewayv2/vpcLink:VpcLink', - 'aws:appautoscaling/target:Target', - 'aws:appconfig/application:Application', - 'aws:appconfig/configurationProfile:ConfigurationProfile', - 'aws:appconfig/deployment:Deployment', - 'aws:appconfig/deploymentStrategy:DeploymentStrategy', - 'aws:appconfig/environment:Environment', - 'aws:appconfig/eventIntegration:EventIntegration', - 'aws:appconfig/extension:Extension', - 'aws:appfabric/appAuthorization:AppAuthorization', - 'aws:appfabric/appBundle:AppBundle', - 'aws:appfabric/ingestion:Ingestion', - 'aws:appfabric/ingestionDestination:IngestionDestination', - 'aws:appflow/flow:Flow', - 'aws:appintegrations/dataIntegration:DataIntegration', - 'aws:applicationinsights/application:Application', - 'aws:appmesh/gatewayRoute:GatewayRoute', - 'aws:appmesh/mesh:Mesh', - 'aws:appmesh/route:Route', - 'aws:appmesh/virtualGateway:VirtualGateway', - 'aws:appmesh/virtualNode:VirtualNode', - 'aws:appmesh/virtualRouter:VirtualRouter', - 'aws:appmesh/virtualService:VirtualService', - 'aws:apprunner/autoScalingConfigurationVersion:AutoScalingConfigurationVersion', - 'aws:apprunner/connection:Connection', - 'aws:apprunner/observabilityConfiguration:ObservabilityConfiguration', - 'aws:apprunner/service:Service', - 'aws:apprunner/vpcConnector:VpcConnector', - 'aws:apprunner/vpcIngressConnection:VpcIngressConnection', - 'aws:appstream/fleet:Fleet', - 'aws:appstream/imageBuilder:ImageBuilder', - 'aws:appstream/stack:Stack', - 'aws:appsync/graphQLApi:GraphQLApi', - 'aws:athena/dataCatalog:DataCatalog', - 'aws:athena/workgroup:Workgroup', - 'aws:auditmanager/assessment:Assessment', - 'aws:auditmanager/control:Control', - 'aws:auditmanager/framework:Framework', - 'aws:autoscaling/group:Group', - 'aws:backup/framework:Framework', - 'aws:backup/logicallyAirGappedVault:LogicallyAirGappedVault', - 'aws:backup/plan:Plan', - 'aws:backup/reportPlan:ReportPlan', - 'aws:backup/vault:Vault', - 'aws:batch/computeEnvironment:ComputeEnvironment', - 'aws:batch/jobDefinition:JobDefinition', - 'aws:batch/jobQueue:JobQueue', - 'aws:batch/schedulingPolicy:SchedulingPolicy', - 'aws:bcmdata/export:Export', - 'aws:bedrock/agentAgent:AgentAgent', - 'aws:bedrock/agentAgentAlias:AgentAgentAlias', - 'aws:bedrock/agentKnowledgeBase:AgentKnowledgeBase', - 'aws:bedrock/customModel:CustomModel', - 'aws:bedrock/guardrail:Guardrail', - 'aws:bedrock/provisionedModelThroughput:ProvisionedModelThroughput', - 'aws:budgets/budget:Budget', - 'aws:budgets/budgetAction:BudgetAction', - 'aws:cfg/aggregateAuthorization:AggregateAuthorization', - 'aws:cfg/configurationAggregator:ConfigurationAggregator', - 'aws:cfg/rule:Rule', - 'aws:chatbot/slackChannelConfiguration:SlackChannelConfiguration', - 'aws:chatbot/teamsChannelConfiguration:TeamsChannelConfiguration', - 'aws:chime/sdkvoiceSipMediaApplication:SdkvoiceSipMediaApplication', - 'aws:chime/sdkvoiceVoiceProfileDomain:SdkvoiceVoiceProfileDomain', - 'aws:chime/voiceConnector:VoiceConnector', - 'aws:chimesdkmediapipelines/mediaInsightsPipelineConfiguration:MediaInsightsPipelineConfiguration', - 'aws:cleanrooms/collaboration:Collaboration', - 'aws:cleanrooms/configuredTable:ConfiguredTable', - 'aws:cloud9/environmentEC2:EnvironmentEC2', - 'aws:cloudformation/stack:Stack', - 'aws:cloudformation/stackSet:StackSet', - 'aws:cloudfront/distribution:Distribution', - 'aws:cloudhsmv2/cluster:Cluster', - 'aws:cloudtrail/eventDataStore:EventDataStore', - 'aws:cloudtrail/trail:Trail', - 'aws:cloudwatch/compositeAlarm:CompositeAlarm', - 'aws:cloudwatch/eventBus:EventBus', - 'aws:cloudwatch/eventRule:EventRule', - 'aws:cloudwatch/internetMonitor:InternetMonitor', - 'aws:cloudwatch/logDestination:LogDestination', - 'aws:cloudwatch/logGroup:LogGroup', - 'aws:cloudwatch/metricAlarm:MetricAlarm', - 'aws:cloudwatch/metricStream:MetricStream', - 'aws:codeartifact/domain:Domain', - 'aws:codeartifact/repository:Repository', - 'aws:codebuild/fleet:Fleet', - 'aws:codebuild/project:Project', - 'aws:codebuild/reportGroup:ReportGroup', - 'aws:codecommit/repository:Repository', - 'aws:codedeploy/application:Application', - 'aws:codedeploy/deploymentGroup:DeploymentGroup', - 'aws:codeguruprofiler/profilingGroup:ProfilingGroup', - 'aws:codegurureviewer/repositoryAssociation:RepositoryAssociation', - 'aws:codepipeline/customActionType:CustomActionType', - 'aws:codepipeline/pipeline:Pipeline', - 'aws:codepipeline/webhook:Webhook', - 'aws:codestarconnections/connection:Connection', - 'aws:codestarnotifications/notificationRule:NotificationRule', - 'aws:cognito/identityPool:IdentityPool', - 'aws:cognito/userPool:UserPool', - 'aws:comprehend/documentClassifier:DocumentClassifier', - 'aws:comprehend/entityRecognizer:EntityRecognizer', - 'aws:connect/contactFlow:ContactFlow', - 'aws:connect/contactFlowModule:ContactFlowModule', - 'aws:connect/hoursOfOperation:HoursOfOperation', - 'aws:connect/instance:Instance', - 'aws:connect/phoneNumber:PhoneNumber', - 'aws:connect/queue:Queue', - 'aws:connect/quickConnect:QuickConnect', - 'aws:connect/routingProfile:RoutingProfile', - 'aws:connect/securityProfile:SecurityProfile', - 'aws:connect/user:User', - 'aws:connect/userHierarchyGroup:UserHierarchyGroup', - 'aws:connect/vocabulary:Vocabulary', - 'aws:controltower/landingZone:LandingZone', - 'aws:costexplorer/anomalyMonitor:AnomalyMonitor', - 'aws:costexplorer/anomalySubscription:AnomalySubscription', - 'aws:costexplorer/costCategory:CostCategory', - 'aws:cur/reportDefinition:ReportDefinition', - 'aws:customerprofiles/domain:Domain', - 'aws:dataexchange/dataSet:DataSet', - 'aws:dataexchange/revision:Revision', - 'aws:datapipeline/pipeline:Pipeline', - 'aws:datasync/agent:Agent', - 'aws:datasync/efsLocation:EfsLocation', - 'aws:datasync/fsxOpenZfsFileSystem:FsxOpenZfsFileSystem', - 'aws:datasync/locationAzureBlob:LocationAzureBlob', - 'aws:datasync/locationFsxLustre:LocationFsxLustre', - 'aws:datasync/locationFsxOntapFileSystem:LocationFsxOntapFileSystem', - 'aws:datasync/locationFsxWindows:LocationFsxWindows', - 'aws:datasync/locationHdfs:LocationHdfs', - 'aws:datasync/locationObjectStorage:LocationObjectStorage', - 'aws:datasync/locationSmb:LocationSmb', - 'aws:datasync/nfsLocation:NfsLocation', - 'aws:datasync/s3Location:S3Location', - 'aws:datasync/task:Task', - 'aws:datazone/domain:Domain', - 'aws:dax/cluster:Cluster', - 'aws:detective/graph:Graph', - 'aws:devicefarm/devicePool:DevicePool', - 'aws:devicefarm/instanceProfile:InstanceProfile', - 'aws:devicefarm/networkProfile:NetworkProfile', - 'aws:devicefarm/project:Project', - 'aws:devicefarm/testGridProject:TestGridProject', - 'aws:devopsguru/resourceCollection:ResourceCollection', - 'aws:directconnect/connection:Connection', - 'aws:directconnect/hostedPrivateVirtualInterfaceAccepter:HostedPrivateVirtualInterfaceAccepter', - 'aws:directconnect/hostedPublicVirtualInterfaceAccepter:HostedPublicVirtualInterfaceAccepter', - 'aws:directconnect/hostedTransitVirtualInterfaceAcceptor:HostedTransitVirtualInterfaceAcceptor', - 'aws:directconnect/linkAggregationGroup:LinkAggregationGroup', - 'aws:directconnect/privateVirtualInterface:PrivateVirtualInterface', - 'aws:directconnect/publicVirtualInterface:PublicVirtualInterface', - 'aws:directconnect/transitVirtualInterface:TransitVirtualInterface', - 'aws:directoryservice/directory:Directory', - 'aws:directoryservice/serviceRegion:ServiceRegion', - 'aws:dlm/lifecyclePolicy:LifecyclePolicy', - 'aws:dms/certificate:Certificate', - 'aws:dms/endpoint:Endpoint', - 'aws:dms/eventSubscription:EventSubscription', - 'aws:dms/replicationConfig:ReplicationConfig', - 'aws:dms/replicationInstance:ReplicationInstance', - 'aws:dms/replicationSubnetGroup:ReplicationSubnetGroup', - 'aws:dms/replicationTask:ReplicationTask', - 'aws:dms/s3Endpoint:S3Endpoint', - 'aws:docdb/cluster:Cluster', - 'aws:docdb/clusterInstance:ClusterInstance', - 'aws:docdb/clusterParameterGroup:ClusterParameterGroup', - 'aws:docdb/elasticCluster:ElasticCluster', - 'aws:docdb/eventSubscription:EventSubscription', - 'aws:docdb/subnetGroup:SubnetGroup', - 'aws:drs/replicationConfigurationTemplate:ReplicationConfigurationTemplate', - 'aws:dynamodb/table:Table', - 'aws:dynamodb/tableReplica:TableReplica', - 'aws:ebs/snapshot:Snapshot', - 'aws:ebs/snapshotCopy:SnapshotCopy', - 'aws:ebs/snapshotImport:SnapshotImport', - 'aws:ebs/volume:Volume', - 'aws:ec2/ami:Ami', - 'aws:ec2/amiCopy:AmiCopy', - 'aws:ec2/amiFromInstance:AmiFromInstance', - 'aws:ec2/capacityBlockReservation:CapacityBlockReservation', - 'aws:ec2/capacityReservation:CapacityReservation', - 'aws:ec2/carrierGateway:CarrierGateway', - 'aws:ec2/customerGateway:CustomerGateway', - 'aws:ec2/dedicatedHost:DedicatedHost', - 'aws:ec2/defaultNetworkAcl:DefaultNetworkAcl', - 'aws:ec2/defaultRouteTable:DefaultRouteTable', - 'aws:ec2/defaultSecurityGroup:DefaultSecurityGroup', - 'aws:ec2/defaultSubnet:DefaultSubnet', - 'aws:ec2/defaultVpc:DefaultVpc', - 'aws:ec2/defaultVpcDhcpOptions:DefaultVpcDhcpOptions', - 'aws:ec2/egressOnlyInternetGateway:EgressOnlyInternetGateway', - 'aws:ec2/eip:Eip', - 'aws:ec2/fleet:Fleet', - 'aws:ec2/flowLog:FlowLog', - 'aws:ec2/instance:Instance', - 'aws:ec2/internetGateway:InternetGateway', - 'aws:ec2/keyPair:KeyPair', - 'aws:ec2/launchTemplate:LaunchTemplate', - 'aws:ec2/localGatewayRouteTableVpcAssociation:LocalGatewayRouteTableVpcAssociation', - 'aws:ec2/managedPrefixList:ManagedPrefixList', - 'aws:ec2/natGateway:NatGateway', - 'aws:ec2/networkAcl:NetworkAcl', - 'aws:ec2/networkInsightsAnalysis:NetworkInsightsAnalysis', - 'aws:ec2/networkInsightsPath:NetworkInsightsPath', - 'aws:ec2/networkInterface:NetworkInterface', - 'aws:ec2/placementGroup:PlacementGroup', - 'aws:ec2/routeTable:RouteTable', - 'aws:ec2/securityGroup:SecurityGroup', - 'aws:ec2/spotFleetRequest:SpotFleetRequest', - 'aws:ec2/spotInstanceRequest:SpotInstanceRequest', - 'aws:ec2/subnet:Subnet', - 'aws:ec2/trafficMirrorFilter:TrafficMirrorFilter', - 'aws:ec2/trafficMirrorSession:TrafficMirrorSession', - 'aws:ec2/trafficMirrorTarget:TrafficMirrorTarget', - 'aws:ec2/vpc:Vpc', - 'aws:ec2/vpcDhcpOptions:VpcDhcpOptions', - 'aws:ec2/vpcEndpoint:VpcEndpoint', - 'aws:ec2/vpcEndpointService:VpcEndpointService', - 'aws:ec2/vpcIpam:VpcIpam', - 'aws:ec2/vpcIpamPool:VpcIpamPool', - 'aws:ec2/vpcIpamResourceDiscovery:VpcIpamResourceDiscovery', - 'aws:ec2/vpcIpamResourceDiscoveryAssociation:VpcIpamResourceDiscoveryAssociation', - 'aws:ec2/vpcIpamScope:VpcIpamScope', - 'aws:ec2/vpcPeeringConnection:VpcPeeringConnection', - 'aws:ec2/vpcPeeringConnectionAccepter:VpcPeeringConnectionAccepter', - 'aws:ec2/vpnConnection:VpnConnection', - 'aws:ec2/vpnGateway:VpnGateway', - 'aws:ec2clientvpn/endpoint:Endpoint', - 'aws:ec2transitgateway/connect:Connect', - 'aws:ec2transitgateway/connectPeer:ConnectPeer', - 'aws:ec2transitgateway/instanceConnectEndpoint:InstanceConnectEndpoint', - 'aws:ec2transitgateway/multicastDomain:MulticastDomain', - 'aws:ec2transitgateway/peeringAttachment:PeeringAttachment', - 'aws:ec2transitgateway/peeringAttachmentAccepter:PeeringAttachmentAccepter', - 'aws:ec2transitgateway/policyTable:PolicyTable', - 'aws:ec2transitgateway/routeTable:RouteTable', - 'aws:ec2transitgateway/transitGateway:TransitGateway', - 'aws:ec2transitgateway/vpcAttachment:VpcAttachment', - 'aws:ec2transitgateway/vpcAttachmentAccepter:VpcAttachmentAccepter', - 'aws:ecr/repository:Repository', - 'aws:ecrpublic/repository:Repository', - 'aws:ecs/capacityProvider:CapacityProvider', - 'aws:ecs/cluster:Cluster', - 'aws:ecs/service:Service', - 'aws:ecs/taskDefinition:TaskDefinition', - 'aws:ecs/taskSet:TaskSet', - 'aws:efs/accessPoint:AccessPoint', - 'aws:efs/fileSystem:FileSystem', - 'aws:eks/accessEntry:AccessEntry', - 'aws:eks/addon:Addon', - 'aws:eks/cluster:Cluster', - 'aws:eks/fargateProfile:FargateProfile', - 'aws:eks/identityProviderConfig:IdentityProviderConfig', - 'aws:eks/nodeGroup:NodeGroup', - 'aws:eks/podIdentityAssociation:PodIdentityAssociation', - 'aws:elasticache/cluster:Cluster', - 'aws:elasticache/parameterGroup:ParameterGroup', - 'aws:elasticache/replicationGroup:ReplicationGroup', - 'aws:elasticache/reservedCacheNode:ReservedCacheNode', - 'aws:elasticache/serverlessCache:ServerlessCache', - 'aws:elasticache/subnetGroup:SubnetGroup', - 'aws:elasticache/user:User', - 'aws:elasticache/userGroup:UserGroup', - 'aws:elasticbeanstalk/application:Application', - 'aws:elasticbeanstalk/applicationVersion:ApplicationVersion', - 'aws:elasticbeanstalk/environment:Environment', - 'aws:elasticsearch/domain:Domain', - 'aws:elb/loadBalancer:LoadBalancer', - 'aws:emr/cluster:Cluster', - 'aws:emr/studio:Studio', - 'aws:emrcontainers/jobTemplate:JobTemplate', - 'aws:emrcontainers/virtualCluster:VirtualCluster', - 'aws:emrserverless/application:Application', - 'aws:evidently/feature:Feature', - 'aws:evidently/launch:Launch', - 'aws:evidently/project:Project', - 'aws:evidently/segment:Segment', - 'aws:finspace/kxCluster:KxCluster', - 'aws:finspace/kxDatabase:KxDatabase', - 'aws:finspace/kxDataview:KxDataview', - 'aws:finspace/kxEnvironment:KxEnvironment', - 'aws:finspace/kxScalingGroup:KxScalingGroup', - 'aws:finspace/kxUser:KxUser', - 'aws:finspace/kxVolume:KxVolume', - 'aws:fis/experimentTemplate:ExperimentTemplate', - 'aws:fms/policy:Policy', - 'aws:fms/resourceSet:ResourceSet', - 'aws:fsx/backup:Backup', - 'aws:fsx/dataRepositoryAssociation:DataRepositoryAssociation', - 'aws:fsx/fileCache:FileCache', - 'aws:fsx/lustreFileSystem:LustreFileSystem', - 'aws:fsx/ontapFileSystem:OntapFileSystem', - 'aws:fsx/ontapStorageVirtualMachine:OntapStorageVirtualMachine', - 'aws:fsx/ontapVolume:OntapVolume', - 'aws:fsx/openZfsFileSystem:OpenZfsFileSystem', - 'aws:fsx/openZfsSnapshot:OpenZfsSnapshot', - 'aws:fsx/openZfsVolume:OpenZfsVolume', - 'aws:fsx/windowsFileSystem:WindowsFileSystem', - 'aws:gamelift/alias:Alias', - 'aws:gamelift/build:Build', - 'aws:gamelift/fleet:Fleet', - 'aws:gamelift/gameServerGroup:GameServerGroup', - 'aws:gamelift/gameSessionQueue:GameSessionQueue', - 'aws:gamelift/matchmakingConfiguration:MatchmakingConfiguration', - 'aws:gamelift/matchmakingRuleSet:MatchmakingRuleSet', - 'aws:gamelift/script:Script', - 'aws:glacier/vault:Vault', - 'aws:globalaccelerator/accelerator:Accelerator', - 'aws:globalaccelerator/crossAccountAttachment:CrossAccountAttachment', - 'aws:globalaccelerator/customRoutingAccelerator:CustomRoutingAccelerator', - 'aws:glue/catalogDatabase:CatalogDatabase', - 'aws:glue/connection:Connection', - 'aws:glue/crawler:Crawler', - 'aws:glue/dataQualityRuleset:DataQualityRuleset', - 'aws:glue/devEndpoint:DevEndpoint', - 'aws:glue/job:Job', - 'aws:glue/mLTransform:MLTransform', - 'aws:glue/registry:Registry', - 'aws:glue/schema:Schema', - 'aws:glue/trigger:Trigger', - 'aws:glue/workflow:Workflow', - 'aws:grafana/workspace:Workspace', - 'aws:guardduty/detector:Detector', - 'aws:guardduty/filter:Filter', - 'aws:guardduty/iPSet:IPSet', - 'aws:guardduty/malwareProtectionPlan:MalwareProtectionPlan', - 'aws:guardduty/threatIntelSet:ThreatIntelSet', - 'aws:iam/instanceProfile:InstanceProfile', - 'aws:iam/openIdConnectProvider:OpenIdConnectProvider', - 'aws:iam/policy:Policy', - 'aws:iam/role:Role', - 'aws:iam/samlProvider:SamlProvider', - 'aws:iam/serverCertificate:ServerCertificate', - 'aws:iam/serviceLinkedRole:ServiceLinkedRole', - 'aws:iam/user:User', - 'aws:iam/virtualMfaDevice:VirtualMfaDevice', - 'aws:imagebuilder/component:Component', - 'aws:imagebuilder/containerRecipe:ContainerRecipe', - 'aws:imagebuilder/distributionConfiguration:DistributionConfiguration', - 'aws:imagebuilder/image:Image', - 'aws:imagebuilder/imagePipeline:ImagePipeline', - 'aws:imagebuilder/imageRecipe:ImageRecipe', - 'aws:imagebuilder/infrastructureConfiguration:InfrastructureConfiguration', - 'aws:imagebuilder/workflow:Workflow', - 'aws:inspector/assessmentTemplate:AssessmentTemplate', - 'aws:inspector/resourceGroup:ResourceGroup', - 'aws:iot/authorizer:Authorizer', - 'aws:iot/billingGroup:BillingGroup', - 'aws:iot/caCertificate:CaCertificate', - 'aws:iot/domainConfiguration:DomainConfiguration', - 'aws:iot/policy:Policy', - 'aws:iot/provisioningTemplate:ProvisioningTemplate', - 'aws:iot/roleAlias:RoleAlias', - 'aws:iot/thingGroup:ThingGroup', - 'aws:iot/thingType:ThingType', - 'aws:iot/topicRule:TopicRule', - 'aws:ivs/channel:Channel', - 'aws:ivs/playbackKeyPair:PlaybackKeyPair', - 'aws:ivs/recordingConfiguration:RecordingConfiguration', - 'aws:ivschat/loggingConfiguration:LoggingConfiguration', - 'aws:ivschat/room:Room', - 'aws:kendra/dataSource:DataSource', - 'aws:kendra/faq:Faq', - 'aws:kendra/index:Index', - 'aws:kendra/querySuggestionsBlockList:QuerySuggestionsBlockList', - 'aws:kendra/thesaurus:Thesaurus', - 'aws:keyspaces/keyspace:Keyspace', - 'aws:keyspaces/table:Table', - 'aws:kinesis/analyticsApplication:AnalyticsApplication', - 'aws:kinesis/firehoseDeliveryStream:FirehoseDeliveryStream', - 'aws:kinesis/stream:Stream', - 'aws:kinesis/videoStream:VideoStream', - 'aws:kinesisanalyticsv2/application:Application', - 'aws:kms/externalKey:ExternalKey', - 'aws:kms/key:Key', - 'aws:kms/replicaExternalKey:ReplicaExternalKey', - 'aws:kms/replicaKey:ReplicaKey', - 'aws:lambda/callbackFunction:CallbackFunction', - 'aws:lambda/codeSigningConfig:CodeSigningConfig', - 'aws:lambda/eventSourceMapping:EventSourceMapping', - 'aws:lambda/function:Function', - 'aws:lb/listener:Listener', - 'aws:lb/listenerRule:ListenerRule', - 'aws:lb/loadBalancer:LoadBalancer', - 'aws:lb/targetGroup:TargetGroup', - 'aws:lb/trustStore:TrustStore', - 'aws:lex/v2modelsBot:V2modelsBot', - 'aws:licensemanager/licenseConfiguration:LicenseConfiguration', - 'aws:lightsail/bucket:Bucket', - 'aws:lightsail/certificate:Certificate', - 'aws:lightsail/containerService:ContainerService', - 'aws:lightsail/database:Database', - 'aws:lightsail/disk:Disk', - 'aws:lightsail/distribution:Distribution', - 'aws:lightsail/instance:Instance', - 'aws:lightsail/keyPair:KeyPair', - 'aws:lightsail/lb:Lb', - 'aws:location/geofenceCollection:GeofenceCollection', - 'aws:location/map:Map', - 'aws:location/placeIndex:PlaceIndex', - 'aws:location/routeCalculation:RouteCalculation', - 'aws:location/tracker:Tracker', - 'aws:m2/application:Application', - 'aws:m2/environment:Environment', - 'aws:macie/customDataIdentifier:CustomDataIdentifier', - 'aws:macie/findingsFilter:FindingsFilter', - 'aws:macie2/classificationJob:ClassificationJob', - 'aws:macie2/member:Member', - 'aws:mediaconvert/queue:Queue', - 'aws:medialive/channel:Channel', - 'aws:medialive/input:Input', - 'aws:medialive/inputSecurityGroup:InputSecurityGroup', - 'aws:medialive/multiplex:Multiplex', - 'aws:mediapackage/channel:Channel', - 'aws:mediastore/container:Container', - 'aws:memorydb/acl:Acl', - 'aws:memorydb/cluster:Cluster', - 'aws:memorydb/parameterGroup:ParameterGroup', - 'aws:memorydb/snapshot:Snapshot', - 'aws:memorydb/subnetGroup:SubnetGroup', - 'aws:memorydb/user:User', - 'aws:mq/broker:Broker', - 'aws:mq/configuration:Configuration', - 'aws:msk/cluster:Cluster', - 'aws:msk/replicator:Replicator', - 'aws:msk/serverlessCluster:ServerlessCluster', - 'aws:msk/vpcConnection:VpcConnection', - 'aws:mskconnect/connector:Connector', - 'aws:mskconnect/customPlugin:CustomPlugin', - 'aws:mskconnect/workerConfiguration:WorkerConfiguration', - 'aws:mwaa/environment:Environment', - 'aws:neptune/cluster:Cluster', - 'aws:neptune/clusterEndpoint:ClusterEndpoint', - 'aws:neptune/clusterInstance:ClusterInstance', - 'aws:neptune/clusterParameterGroup:ClusterParameterGroup', - 'aws:neptune/eventSubscription:EventSubscription', - 'aws:neptune/parameterGroup:ParameterGroup', - 'aws:neptune/subnetGroup:SubnetGroup', - 'aws:networkfirewall/firewall:Firewall', - 'aws:networkfirewall/firewallPolicy:FirewallPolicy', - 'aws:networkfirewall/ruleGroup:RuleGroup', - 'aws:networkfirewall/tlsInspectionConfiguration:TlsInspectionConfiguration', - 'aws:networkmanager/connectAttachment:ConnectAttachment', - 'aws:networkmanager/connectPeer:ConnectPeer', - 'aws:networkmanager/connection:Connection', - 'aws:networkmanager/coreNetwork:CoreNetwork', - 'aws:networkmanager/device:Device', - 'aws:networkmanager/globalNetwork:GlobalNetwork', - 'aws:networkmanager/link:Link', - 'aws:networkmanager/site:Site', - 'aws:networkmanager/siteToSiteVpnAttachment:SiteToSiteVpnAttachment', - 'aws:networkmanager/transitGatewayPeering:TransitGatewayPeering', - 'aws:networkmanager/transitGatewayRouteTableAttachment:TransitGatewayRouteTableAttachment', - 'aws:networkmanager/vpcAttachment:VpcAttachment', - 'aws:networkmonitor/monitor:Monitor', - 'aws:networkmonitor/probe:Probe', - 'aws:oam/link:Link', - 'aws:oam/sink:Sink', - 'aws:opensearch/domain:Domain', - 'aws:opensearch/serverlessCollection:ServerlessCollection', - 'aws:opensearchingest/pipeline:Pipeline', - 'aws:opsworks/customLayer:CustomLayer', - 'aws:opsworks/ecsClusterLayer:EcsClusterLayer', - 'aws:opsworks/gangliaLayer:GangliaLayer', - 'aws:opsworks/haproxyLayer:HaproxyLayer', - 'aws:opsworks/javaAppLayer:JavaAppLayer', - 'aws:opsworks/memcachedLayer:MemcachedLayer', - 'aws:opsworks/mysqlLayer:MysqlLayer', - 'aws:opsworks/nodejsAppLayer:NodejsAppLayer', - 'aws:opsworks/phpAppLayer:PhpAppLayer', - 'aws:opsworks/railsAppLayer:RailsAppLayer', - 'aws:opsworks/stack:Stack', - 'aws:opsworks/staticWebLayer:StaticWebLayer', - 'aws:organizations/account:Account', - 'aws:organizations/organizationalUnit:OrganizationalUnit', - 'aws:organizations/policy:Policy', - 'aws:organizations/resourcePolicy:ResourcePolicy', - 'aws:paymentcryptography/key:Key', - 'aws:pinpoint/app:App', - 'aws:pinpoint/emailTemplate:EmailTemplate', - 'aws:pinpoint/smsvoicev2OptOutList:Smsvoicev2OptOutList', - 'aws:pinpoint/smsvoicev2PhoneNumber:Smsvoicev2PhoneNumber', - 'aws:pipes/pipe:Pipe', - 'aws:qldb/ledger:Ledger', - 'aws:qldb/stream:Stream', - 'aws:quicksight/analysis:Analysis', - 'aws:quicksight/dashboard:Dashboard', - 'aws:quicksight/dataSet:DataSet', - 'aws:quicksight/dataSource:DataSource', - 'aws:quicksight/folder:Folder', - 'aws:quicksight/namespace:Namespace', - 'aws:quicksight/template:Template', - 'aws:quicksight/theme:Theme', - 'aws:quicksight/vpcConnection:VpcConnection', - 'aws:ram/resourceShare:ResourceShare', - 'aws:rbin/rule:Rule', - 'aws:rds/cluster:Cluster', - 'aws:rds/clusterEndpoint:ClusterEndpoint', - 'aws:rds/clusterInstance:ClusterInstance', - 'aws:rds/clusterParameterGroup:ClusterParameterGroup', - 'aws:rds/clusterSnapshot:ClusterSnapshot', - 'aws:rds/customDbEngineVersion:CustomDbEngineVersion', - 'aws:rds/eventSubscription:EventSubscription', - 'aws:rds/instance:Instance', - 'aws:rds/integration:Integration', - 'aws:rds/optionGroup:OptionGroup', - 'aws:rds/parameterGroup:ParameterGroup', - 'aws:rds/proxy:Proxy', - 'aws:rds/proxyEndpoint:ProxyEndpoint', - 'aws:rds/reservedInstance:ReservedInstance', - 'aws:rds/snapshot:Snapshot', - 'aws:rds/snapshotCopy:SnapshotCopy', - 'aws:rds/subnetGroup:SubnetGroup', - 'aws:redshift/cluster:Cluster', - 'aws:redshift/clusterSnapshot:ClusterSnapshot', - 'aws:redshift/eventSubscription:EventSubscription', - 'aws:redshift/hsmClientCertificate:HsmClientCertificate', - 'aws:redshift/hsmConfiguration:HsmConfiguration', - 'aws:redshift/parameterGroup:ParameterGroup', - 'aws:redshift/snapshotCopyGrant:SnapshotCopyGrant', - 'aws:redshift/snapshotSchedule:SnapshotSchedule', - 'aws:redshift/subnetGroup:SubnetGroup', - 'aws:redshift/usageLimit:UsageLimit', - 'aws:redshiftserverless/namespace:Namespace', - 'aws:redshiftserverless/workgroup:Workgroup', - 'aws:rekognition/collection:Collection', - 'aws:rekognition/streamProcessor:StreamProcessor', - 'aws:resourceexplorer/index:Index', - 'aws:resourceexplorer/view:View', - 'aws:resourcegroups/group:Group', - 'aws:rolesanywhere/profile:Profile', - 'aws:rolesanywhere/trustAnchor:TrustAnchor', - 'aws:route53/healthCheck:HealthCheck', - 'aws:route53/resolverEndpoint:ResolverEndpoint', - 'aws:route53/resolverFirewallDomainList:ResolverFirewallDomainList', - 'aws:route53/resolverFirewallRuleGroup:ResolverFirewallRuleGroup', - 'aws:route53/resolverFirewallRuleGroupAssociation:ResolverFirewallRuleGroupAssociation', - 'aws:route53/resolverQueryLogConfig:ResolverQueryLogConfig', - 'aws:route53/resolverRule:ResolverRule', - 'aws:route53/zone:Zone', - 'aws:route53domains/registeredDomain:RegisteredDomain', - 'aws:route53recoveryreadiness/cell:Cell', - 'aws:route53recoveryreadiness/readinessCheck:ReadinessCheck', - 'aws:route53recoveryreadiness/recoveryGroup:RecoveryGroup', - 'aws:route53recoveryreadiness/resourceSet:ResourceSet', - 'aws:rum/appMonitor:AppMonitor', - 'aws:s3/bucket:Bucket', - 'aws:s3/bucketObject:BucketObject', - 'aws:s3/bucketObjectv2:BucketObjectv2', - 'aws:s3/bucketV2:BucketV2', - 'aws:s3/objectCopy:ObjectCopy', - 'aws:s3control/accessGrant:AccessGrant', - 'aws:s3control/accessGrantsInstance:AccessGrantsInstance', - 'aws:s3control/accessGrantsLocation:AccessGrantsLocation', - 'aws:s3control/bucket:Bucket', - 'aws:s3control/storageLensConfiguration:StorageLensConfiguration', - 'aws:sagemaker/app:App', - 'aws:sagemaker/appImageConfig:AppImageConfig', - 'aws:sagemaker/codeRepository:CodeRepository', - 'aws:sagemaker/dataQualityJobDefinition:DataQualityJobDefinition', - 'aws:sagemaker/deviceFleet:DeviceFleet', - 'aws:sagemaker/domain:Domain', - 'aws:sagemaker/endpoint:Endpoint', - 'aws:sagemaker/endpointConfiguration:EndpointConfiguration', - 'aws:sagemaker/featureGroup:FeatureGroup', - 'aws:sagemaker/flowDefinition:FlowDefinition', - 'aws:sagemaker/humanTaskUI:HumanTaskUI', - 'aws:sagemaker/image:Image', - 'aws:sagemaker/model:Model', - 'aws:sagemaker/modelPackageGroup:ModelPackageGroup', - 'aws:sagemaker/monitoringSchedule:MonitoringSchedule', - 'aws:sagemaker/notebookInstance:NotebookInstance', - 'aws:sagemaker/pipeline:Pipeline', - 'aws:sagemaker/project:Project', - 'aws:sagemaker/space:Space', - 'aws:sagemaker/studioLifecycleConfig:StudioLifecycleConfig', - 'aws:sagemaker/userProfile:UserProfile', - 'aws:sagemaker/workteam:Workteam', - 'aws:scheduler/scheduleGroup:ScheduleGroup', - 'aws:schemas/discoverer:Discoverer', - 'aws:schemas/registry:Registry', - 'aws:schemas/schema:Schema', - 'aws:secretsmanager/secret:Secret', - 'aws:securityhub/automationRule:AutomationRule', - 'aws:securitylake/dataLake:DataLake', - 'aws:securitylake/subscriber:Subscriber', - 'aws:serverlessrepository/cloudFormationStack:CloudFormationStack', - 'aws:servicecatalog/portfolio:Portfolio', - 'aws:servicecatalog/product:Product', - 'aws:servicecatalog/provisionedProduct:ProvisionedProduct', - 'aws:servicediscovery/httpNamespace:HttpNamespace', - 'aws:servicediscovery/privateDnsNamespace:PrivateDnsNamespace', - 'aws:servicediscovery/publicDnsNamespace:PublicDnsNamespace', - 'aws:servicediscovery/service:Service', - 'aws:sesv2/configurationSet:ConfigurationSet', - 'aws:sesv2/contactList:ContactList', - 'aws:sesv2/dedicatedIpPool:DedicatedIpPool', - 'aws:sesv2/emailIdentity:EmailIdentity', - 'aws:sfn/activity:Activity', - 'aws:sfn/stateMachine:StateMachine', - 'aws:shield/protection:Protection', - 'aws:shield/protectionGroup:ProtectionGroup', - 'aws:signer/signingProfile:SigningProfile', - 'aws:sns/topic:Topic', - 'aws:sqs/queue:Queue', - 'aws:ssm/activation:Activation', - 'aws:ssm/association:Association', - 'aws:ssm/contactsRotation:ContactsRotation', - 'aws:ssm/document:Document', - 'aws:ssm/maintenanceWindow:MaintenanceWindow', - 'aws:ssm/parameter:Parameter', - 'aws:ssm/patchBaseline:PatchBaseline', - 'aws:ssmcontacts/contact:Contact', - 'aws:ssmincidents/replicationSet:ReplicationSet', - 'aws:ssmincidents/responsePlan:ResponsePlan', - 'aws:ssoadmin/application:Application', - 'aws:ssoadmin/permissionSet:PermissionSet', - 'aws:ssoadmin/trustedTokenIssuer:TrustedTokenIssuer', - 'aws:storagegateway/cachesIscsiVolume:CachesIscsiVolume', - 'aws:storagegateway/fileSystemAssociation:FileSystemAssociation', - 'aws:storagegateway/gateway:Gateway', - 'aws:storagegateway/nfsFileShare:NfsFileShare', - 'aws:storagegateway/smbFileShare:SmbFileShare', - 'aws:storagegateway/storedIscsiVolume:StoredIscsiVolume', - 'aws:storagegateway/tapePool:TapePool', - 'aws:swf/domain:Domain', - 'aws:synthetics/canary:Canary', - 'aws:synthetics/group:Group', - 'aws:timestreaminfluxdb/dbInstance:DbInstance', - 'aws:timestreamwrite/database:Database', - 'aws:timestreamwrite/table:Table', - 'aws:transcribe/languageModel:LanguageModel', - 'aws:transcribe/medicalVocabulary:MedicalVocabulary', - 'aws:transcribe/vocabulary:Vocabulary', - 'aws:transcribe/vocabularyFilter:VocabularyFilter', - 'aws:transfer/agreement:Agreement', - 'aws:transfer/certificate:Certificate', - 'aws:transfer/connector:Connector', - 'aws:transfer/profile:Profile', - 'aws:transfer/server:Server', - 'aws:transfer/user:User', - 'aws:transfer/workflow:Workflow', - 'aws:verifiedaccess/endpoint:Endpoint', - 'aws:verifiedaccess/group:Group', - 'aws:verifiedaccess/instance:Instance', - 'aws:verifiedaccess/trustProvider:TrustProvider', - 'aws:vpc/securityGroupEgressRule:SecurityGroupEgressRule', - 'aws:vpc/securityGroupIngressRule:SecurityGroupIngressRule', - 'aws:vpclattice/accessLogSubscription:AccessLogSubscription', - 'aws:vpclattice/listener:Listener', - 'aws:vpclattice/listenerRule:ListenerRule', - 'aws:vpclattice/service:Service', - 'aws:vpclattice/serviceNetwork:ServiceNetwork', - 'aws:vpclattice/serviceNetworkServiceAssociation:ServiceNetworkServiceAssociation', - 'aws:vpclattice/serviceNetworkVpcAssociation:ServiceNetworkVpcAssociation', - 'aws:vpclattice/targetGroup:TargetGroup', - 'aws:waf/rateBasedRule:RateBasedRule', - 'aws:waf/rule:Rule', - 'aws:waf/ruleGroup:RuleGroup', - 'aws:waf/webAcl:WebAcl', - 'aws:wafregional/rateBasedRule:RateBasedRule', - 'aws:wafregional/rule:Rule', - 'aws:wafregional/ruleGroup:RuleGroup', - 'aws:wafregional/webAcl:WebAcl', - 'aws:wafv2/ipSet:IpSet', - 'aws:wafv2/regexPatternSet:RegexPatternSet', - 'aws:wafv2/ruleGroup:RuleGroup', - 'aws:wafv2/webAcl:WebAcl', - 'aws:workspaces/connectionAlias:ConnectionAlias', - 'aws:workspaces/directory:Directory', - 'aws:workspaces/ipGroup:IpGroup', - 'aws:workspaces/workspace:Workspace', - 'aws:xray/group:Group', - 'aws:xray/samplingRule:SamplingRule', + "aws:accessanalyzer/analyzer:Analyzer", + "aws:acm/certificate:Certificate", + "aws:acmpca/certificateAuthority:CertificateAuthority", + "aws:alb/listener:Listener", + "aws:alb/listenerRule:ListenerRule", + "aws:alb/loadBalancer:LoadBalancer", + "aws:alb/targetGroup:TargetGroup", + "aws:amp/scraper:Scraper", + "aws:amp/workspace:Workspace", + "aws:amplify/app:App", + "aws:amplify/branch:Branch", + "aws:apigateway/apiKey:ApiKey", + "aws:apigateway/clientCertificate:ClientCertificate", + "aws:apigateway/domainName:DomainName", + "aws:apigateway/restApi:RestApi", + "aws:apigateway/stage:Stage", + "aws:apigateway/usagePlan:UsagePlan", + "aws:apigateway/vpcLink:VpcLink", + "aws:apigatewayv2/api:Api", + "aws:apigatewayv2/domainName:DomainName", + "aws:apigatewayv2/stage:Stage", + "aws:apigatewayv2/vpcLink:VpcLink", + "aws:appautoscaling/target:Target", + "aws:appconfig/application:Application", + "aws:appconfig/configurationProfile:ConfigurationProfile", + "aws:appconfig/deployment:Deployment", + "aws:appconfig/deploymentStrategy:DeploymentStrategy", + "aws:appconfig/environment:Environment", + "aws:appconfig/eventIntegration:EventIntegration", + "aws:appconfig/extension:Extension", + "aws:appfabric/appAuthorization:AppAuthorization", + "aws:appfabric/appBundle:AppBundle", + "aws:appfabric/ingestion:Ingestion", + "aws:appfabric/ingestionDestination:IngestionDestination", + "aws:appflow/flow:Flow", + "aws:appintegrations/dataIntegration:DataIntegration", + "aws:applicationinsights/application:Application", + "aws:appmesh/gatewayRoute:GatewayRoute", + "aws:appmesh/mesh:Mesh", + "aws:appmesh/route:Route", + "aws:appmesh/virtualGateway:VirtualGateway", + "aws:appmesh/virtualNode:VirtualNode", + "aws:appmesh/virtualRouter:VirtualRouter", + "aws:appmesh/virtualService:VirtualService", + "aws:apprunner/autoScalingConfigurationVersion:AutoScalingConfigurationVersion", + "aws:apprunner/connection:Connection", + "aws:apprunner/observabilityConfiguration:ObservabilityConfiguration", + "aws:apprunner/service:Service", + "aws:apprunner/vpcConnector:VpcConnector", + "aws:apprunner/vpcIngressConnection:VpcIngressConnection", + "aws:appstream/fleet:Fleet", + "aws:appstream/imageBuilder:ImageBuilder", + "aws:appstream/stack:Stack", + "aws:appsync/graphQLApi:GraphQLApi", + "aws:athena/dataCatalog:DataCatalog", + "aws:athena/workgroup:Workgroup", + "aws:auditmanager/assessment:Assessment", + "aws:auditmanager/control:Control", + "aws:auditmanager/framework:Framework", + "aws:autoscaling/group:Group", + "aws:backup/framework:Framework", + "aws:backup/logicallyAirGappedVault:LogicallyAirGappedVault", + "aws:backup/plan:Plan", + "aws:backup/reportPlan:ReportPlan", + "aws:backup/vault:Vault", + "aws:batch/computeEnvironment:ComputeEnvironment", + "aws:batch/jobDefinition:JobDefinition", + "aws:batch/jobQueue:JobQueue", + "aws:batch/schedulingPolicy:SchedulingPolicy", + "aws:bcmdata/export:Export", + "aws:bedrock/agentAgent:AgentAgent", + "aws:bedrock/agentAgentAlias:AgentAgentAlias", + "aws:bedrock/agentKnowledgeBase:AgentKnowledgeBase", + "aws:bedrock/customModel:CustomModel", + "aws:bedrock/guardrail:Guardrail", + "aws:bedrock/provisionedModelThroughput:ProvisionedModelThroughput", + "aws:budgets/budget:Budget", + "aws:budgets/budgetAction:BudgetAction", + "aws:cfg/aggregateAuthorization:AggregateAuthorization", + "aws:cfg/configurationAggregator:ConfigurationAggregator", + "aws:cfg/rule:Rule", + "aws:chatbot/slackChannelConfiguration:SlackChannelConfiguration", + "aws:chatbot/teamsChannelConfiguration:TeamsChannelConfiguration", + "aws:chime/sdkvoiceSipMediaApplication:SdkvoiceSipMediaApplication", + "aws:chime/sdkvoiceVoiceProfileDomain:SdkvoiceVoiceProfileDomain", + "aws:chime/voiceConnector:VoiceConnector", + "aws:chimesdkmediapipelines/mediaInsightsPipelineConfiguration:MediaInsightsPipelineConfiguration", + "aws:cleanrooms/collaboration:Collaboration", + "aws:cleanrooms/configuredTable:ConfiguredTable", + "aws:cloud9/environmentEC2:EnvironmentEC2", + "aws:cloudformation/stack:Stack", + "aws:cloudformation/stackSet:StackSet", + "aws:cloudfront/distribution:Distribution", + "aws:cloudhsmv2/cluster:Cluster", + "aws:cloudtrail/eventDataStore:EventDataStore", + "aws:cloudtrail/trail:Trail", + "aws:cloudwatch/compositeAlarm:CompositeAlarm", + "aws:cloudwatch/eventBus:EventBus", + "aws:cloudwatch/eventRule:EventRule", + "aws:cloudwatch/internetMonitor:InternetMonitor", + "aws:cloudwatch/logDestination:LogDestination", + "aws:cloudwatch/logGroup:LogGroup", + "aws:cloudwatch/metricAlarm:MetricAlarm", + "aws:cloudwatch/metricStream:MetricStream", + "aws:codeartifact/domain:Domain", + "aws:codeartifact/repository:Repository", + "aws:codebuild/fleet:Fleet", + "aws:codebuild/project:Project", + "aws:codebuild/reportGroup:ReportGroup", + "aws:codecommit/repository:Repository", + "aws:codedeploy/application:Application", + "aws:codedeploy/deploymentGroup:DeploymentGroup", + "aws:codeguruprofiler/profilingGroup:ProfilingGroup", + "aws:codegurureviewer/repositoryAssociation:RepositoryAssociation", + "aws:codepipeline/customActionType:CustomActionType", + "aws:codepipeline/pipeline:Pipeline", + "aws:codepipeline/webhook:Webhook", + "aws:codestarconnections/connection:Connection", + "aws:codestarnotifications/notificationRule:NotificationRule", + "aws:cognito/identityPool:IdentityPool", + "aws:cognito/userPool:UserPool", + "aws:comprehend/documentClassifier:DocumentClassifier", + "aws:comprehend/entityRecognizer:EntityRecognizer", + "aws:connect/contactFlow:ContactFlow", + "aws:connect/contactFlowModule:ContactFlowModule", + "aws:connect/hoursOfOperation:HoursOfOperation", + "aws:connect/instance:Instance", + "aws:connect/phoneNumber:PhoneNumber", + "aws:connect/queue:Queue", + "aws:connect/quickConnect:QuickConnect", + "aws:connect/routingProfile:RoutingProfile", + "aws:connect/securityProfile:SecurityProfile", + "aws:connect/user:User", + "aws:connect/userHierarchyGroup:UserHierarchyGroup", + "aws:connect/vocabulary:Vocabulary", + "aws:controltower/landingZone:LandingZone", + "aws:costexplorer/anomalyMonitor:AnomalyMonitor", + "aws:costexplorer/anomalySubscription:AnomalySubscription", + "aws:costexplorer/costCategory:CostCategory", + "aws:cur/reportDefinition:ReportDefinition", + "aws:customerprofiles/domain:Domain", + "aws:dataexchange/dataSet:DataSet", + "aws:dataexchange/revision:Revision", + "aws:datapipeline/pipeline:Pipeline", + "aws:datasync/agent:Agent", + "aws:datasync/efsLocation:EfsLocation", + "aws:datasync/fsxOpenZfsFileSystem:FsxOpenZfsFileSystem", + "aws:datasync/locationAzureBlob:LocationAzureBlob", + "aws:datasync/locationFsxLustre:LocationFsxLustre", + "aws:datasync/locationFsxOntapFileSystem:LocationFsxOntapFileSystem", + "aws:datasync/locationFsxWindows:LocationFsxWindows", + "aws:datasync/locationHdfs:LocationHdfs", + "aws:datasync/locationObjectStorage:LocationObjectStorage", + "aws:datasync/locationSmb:LocationSmb", + "aws:datasync/nfsLocation:NfsLocation", + "aws:datasync/s3Location:S3Location", + "aws:datasync/task:Task", + "aws:datazone/domain:Domain", + "aws:dax/cluster:Cluster", + "aws:detective/graph:Graph", + "aws:devicefarm/devicePool:DevicePool", + "aws:devicefarm/instanceProfile:InstanceProfile", + "aws:devicefarm/networkProfile:NetworkProfile", + "aws:devicefarm/project:Project", + "aws:devicefarm/testGridProject:TestGridProject", + "aws:devopsguru/resourceCollection:ResourceCollection", + "aws:directconnect/connection:Connection", + "aws:directconnect/hostedPrivateVirtualInterfaceAccepter:HostedPrivateVirtualInterfaceAccepter", + "aws:directconnect/hostedPublicVirtualInterfaceAccepter:HostedPublicVirtualInterfaceAccepter", + "aws:directconnect/hostedTransitVirtualInterfaceAcceptor:HostedTransitVirtualInterfaceAcceptor", + "aws:directconnect/linkAggregationGroup:LinkAggregationGroup", + "aws:directconnect/privateVirtualInterface:PrivateVirtualInterface", + "aws:directconnect/publicVirtualInterface:PublicVirtualInterface", + "aws:directconnect/transitVirtualInterface:TransitVirtualInterface", + "aws:directoryservice/directory:Directory", + "aws:directoryservice/serviceRegion:ServiceRegion", + "aws:dlm/lifecyclePolicy:LifecyclePolicy", + "aws:dms/certificate:Certificate", + "aws:dms/endpoint:Endpoint", + "aws:dms/eventSubscription:EventSubscription", + "aws:dms/replicationConfig:ReplicationConfig", + "aws:dms/replicationInstance:ReplicationInstance", + "aws:dms/replicationSubnetGroup:ReplicationSubnetGroup", + "aws:dms/replicationTask:ReplicationTask", + "aws:dms/s3Endpoint:S3Endpoint", + "aws:docdb/cluster:Cluster", + "aws:docdb/clusterInstance:ClusterInstance", + "aws:docdb/clusterParameterGroup:ClusterParameterGroup", + "aws:docdb/elasticCluster:ElasticCluster", + "aws:docdb/eventSubscription:EventSubscription", + "aws:docdb/subnetGroup:SubnetGroup", + "aws:drs/replicationConfigurationTemplate:ReplicationConfigurationTemplate", + "aws:dynamodb/table:Table", + "aws:dynamodb/tableReplica:TableReplica", + "aws:ebs/snapshot:Snapshot", + "aws:ebs/snapshotCopy:SnapshotCopy", + "aws:ebs/snapshotImport:SnapshotImport", + "aws:ebs/volume:Volume", + "aws:ec2/ami:Ami", + "aws:ec2/amiCopy:AmiCopy", + "aws:ec2/amiFromInstance:AmiFromInstance", + "aws:ec2/capacityBlockReservation:CapacityBlockReservation", + "aws:ec2/capacityReservation:CapacityReservation", + "aws:ec2/carrierGateway:CarrierGateway", + "aws:ec2/customerGateway:CustomerGateway", + "aws:ec2/dedicatedHost:DedicatedHost", + "aws:ec2/defaultNetworkAcl:DefaultNetworkAcl", + "aws:ec2/defaultRouteTable:DefaultRouteTable", + "aws:ec2/defaultSecurityGroup:DefaultSecurityGroup", + "aws:ec2/defaultSubnet:DefaultSubnet", + "aws:ec2/defaultVpc:DefaultVpc", + "aws:ec2/defaultVpcDhcpOptions:DefaultVpcDhcpOptions", + "aws:ec2/egressOnlyInternetGateway:EgressOnlyInternetGateway", + "aws:ec2/eip:Eip", + "aws:ec2/fleet:Fleet", + "aws:ec2/flowLog:FlowLog", + "aws:ec2/instance:Instance", + "aws:ec2/internetGateway:InternetGateway", + "aws:ec2/keyPair:KeyPair", + "aws:ec2/launchTemplate:LaunchTemplate", + "aws:ec2/localGatewayRouteTableVpcAssociation:LocalGatewayRouteTableVpcAssociation", + "aws:ec2/managedPrefixList:ManagedPrefixList", + "aws:ec2/natGateway:NatGateway", + "aws:ec2/networkAcl:NetworkAcl", + "aws:ec2/networkInsightsAnalysis:NetworkInsightsAnalysis", + "aws:ec2/networkInsightsPath:NetworkInsightsPath", + "aws:ec2/networkInterface:NetworkInterface", + "aws:ec2/placementGroup:PlacementGroup", + "aws:ec2/routeTable:RouteTable", + "aws:ec2/securityGroup:SecurityGroup", + "aws:ec2/spotFleetRequest:SpotFleetRequest", + "aws:ec2/spotInstanceRequest:SpotInstanceRequest", + "aws:ec2/subnet:Subnet", + "aws:ec2/trafficMirrorFilter:TrafficMirrorFilter", + "aws:ec2/trafficMirrorSession:TrafficMirrorSession", + "aws:ec2/trafficMirrorTarget:TrafficMirrorTarget", + "aws:ec2/vpc:Vpc", + "aws:ec2/vpcDhcpOptions:VpcDhcpOptions", + "aws:ec2/vpcEndpoint:VpcEndpoint", + "aws:ec2/vpcEndpointService:VpcEndpointService", + "aws:ec2/vpcIpam:VpcIpam", + "aws:ec2/vpcIpamPool:VpcIpamPool", + "aws:ec2/vpcIpamResourceDiscovery:VpcIpamResourceDiscovery", + "aws:ec2/vpcIpamResourceDiscoveryAssociation:VpcIpamResourceDiscoveryAssociation", + "aws:ec2/vpcIpamScope:VpcIpamScope", + "aws:ec2/vpcPeeringConnection:VpcPeeringConnection", + "aws:ec2/vpcPeeringConnectionAccepter:VpcPeeringConnectionAccepter", + "aws:ec2/vpnConnection:VpnConnection", + "aws:ec2/vpnGateway:VpnGateway", + "aws:ec2clientvpn/endpoint:Endpoint", + "aws:ec2transitgateway/connect:Connect", + "aws:ec2transitgateway/connectPeer:ConnectPeer", + "aws:ec2transitgateway/instanceConnectEndpoint:InstanceConnectEndpoint", + "aws:ec2transitgateway/multicastDomain:MulticastDomain", + "aws:ec2transitgateway/peeringAttachment:PeeringAttachment", + "aws:ec2transitgateway/peeringAttachmentAccepter:PeeringAttachmentAccepter", + "aws:ec2transitgateway/policyTable:PolicyTable", + "aws:ec2transitgateway/routeTable:RouteTable", + "aws:ec2transitgateway/transitGateway:TransitGateway", + "aws:ec2transitgateway/vpcAttachment:VpcAttachment", + "aws:ec2transitgateway/vpcAttachmentAccepter:VpcAttachmentAccepter", + "aws:ecr/repository:Repository", + "aws:ecrpublic/repository:Repository", + "aws:ecs/capacityProvider:CapacityProvider", + "aws:ecs/cluster:Cluster", + "aws:ecs/service:Service", + "aws:ecs/taskDefinition:TaskDefinition", + "aws:ecs/taskSet:TaskSet", + "aws:efs/accessPoint:AccessPoint", + "aws:efs/fileSystem:FileSystem", + "aws:eks/accessEntry:AccessEntry", + "aws:eks/addon:Addon", + "aws:eks/cluster:Cluster", + "aws:eks/fargateProfile:FargateProfile", + "aws:eks/identityProviderConfig:IdentityProviderConfig", + "aws:eks/nodeGroup:NodeGroup", + "aws:eks/podIdentityAssociation:PodIdentityAssociation", + "aws:elasticache/cluster:Cluster", + "aws:elasticache/parameterGroup:ParameterGroup", + "aws:elasticache/replicationGroup:ReplicationGroup", + "aws:elasticache/reservedCacheNode:ReservedCacheNode", + "aws:elasticache/serverlessCache:ServerlessCache", + "aws:elasticache/subnetGroup:SubnetGroup", + "aws:elasticache/user:User", + "aws:elasticache/userGroup:UserGroup", + "aws:elasticbeanstalk/application:Application", + "aws:elasticbeanstalk/applicationVersion:ApplicationVersion", + "aws:elasticbeanstalk/environment:Environment", + "aws:elasticsearch/domain:Domain", + "aws:elb/loadBalancer:LoadBalancer", + "aws:emr/cluster:Cluster", + "aws:emr/studio:Studio", + "aws:emrcontainers/jobTemplate:JobTemplate", + "aws:emrcontainers/virtualCluster:VirtualCluster", + "aws:emrserverless/application:Application", + "aws:evidently/feature:Feature", + "aws:evidently/launch:Launch", + "aws:evidently/project:Project", + "aws:evidently/segment:Segment", + "aws:finspace/kxCluster:KxCluster", + "aws:finspace/kxDatabase:KxDatabase", + "aws:finspace/kxDataview:KxDataview", + "aws:finspace/kxEnvironment:KxEnvironment", + "aws:finspace/kxScalingGroup:KxScalingGroup", + "aws:finspace/kxUser:KxUser", + "aws:finspace/kxVolume:KxVolume", + "aws:fis/experimentTemplate:ExperimentTemplate", + "aws:fms/policy:Policy", + "aws:fms/resourceSet:ResourceSet", + "aws:fsx/backup:Backup", + "aws:fsx/dataRepositoryAssociation:DataRepositoryAssociation", + "aws:fsx/fileCache:FileCache", + "aws:fsx/lustreFileSystem:LustreFileSystem", + "aws:fsx/ontapFileSystem:OntapFileSystem", + "aws:fsx/ontapStorageVirtualMachine:OntapStorageVirtualMachine", + "aws:fsx/ontapVolume:OntapVolume", + "aws:fsx/openZfsFileSystem:OpenZfsFileSystem", + "aws:fsx/openZfsSnapshot:OpenZfsSnapshot", + "aws:fsx/openZfsVolume:OpenZfsVolume", + "aws:fsx/windowsFileSystem:WindowsFileSystem", + "aws:gamelift/alias:Alias", + "aws:gamelift/build:Build", + "aws:gamelift/fleet:Fleet", + "aws:gamelift/gameServerGroup:GameServerGroup", + "aws:gamelift/gameSessionQueue:GameSessionQueue", + "aws:gamelift/matchmakingConfiguration:MatchmakingConfiguration", + "aws:gamelift/matchmakingRuleSet:MatchmakingRuleSet", + "aws:gamelift/script:Script", + "aws:glacier/vault:Vault", + "aws:globalaccelerator/accelerator:Accelerator", + "aws:globalaccelerator/crossAccountAttachment:CrossAccountAttachment", + "aws:globalaccelerator/customRoutingAccelerator:CustomRoutingAccelerator", + "aws:glue/catalogDatabase:CatalogDatabase", + "aws:glue/connection:Connection", + "aws:glue/crawler:Crawler", + "aws:glue/dataQualityRuleset:DataQualityRuleset", + "aws:glue/devEndpoint:DevEndpoint", + "aws:glue/job:Job", + "aws:glue/mLTransform:MLTransform", + "aws:glue/registry:Registry", + "aws:glue/schema:Schema", + "aws:glue/trigger:Trigger", + "aws:glue/workflow:Workflow", + "aws:grafana/workspace:Workspace", + "aws:guardduty/detector:Detector", + "aws:guardduty/filter:Filter", + "aws:guardduty/iPSet:IPSet", + "aws:guardduty/malwareProtectionPlan:MalwareProtectionPlan", + "aws:guardduty/threatIntelSet:ThreatIntelSet", + "aws:iam/instanceProfile:InstanceProfile", + "aws:iam/openIdConnectProvider:OpenIdConnectProvider", + "aws:iam/policy:Policy", + "aws:iam/role:Role", + "aws:iam/samlProvider:SamlProvider", + "aws:iam/serverCertificate:ServerCertificate", + "aws:iam/serviceLinkedRole:ServiceLinkedRole", + "aws:iam/user:User", + "aws:iam/virtualMfaDevice:VirtualMfaDevice", + "aws:imagebuilder/component:Component", + "aws:imagebuilder/containerRecipe:ContainerRecipe", + "aws:imagebuilder/distributionConfiguration:DistributionConfiguration", + "aws:imagebuilder/image:Image", + "aws:imagebuilder/imagePipeline:ImagePipeline", + "aws:imagebuilder/imageRecipe:ImageRecipe", + "aws:imagebuilder/infrastructureConfiguration:InfrastructureConfiguration", + "aws:imagebuilder/workflow:Workflow", + "aws:inspector/assessmentTemplate:AssessmentTemplate", + "aws:inspector/resourceGroup:ResourceGroup", + "aws:iot/authorizer:Authorizer", + "aws:iot/billingGroup:BillingGroup", + "aws:iot/caCertificate:CaCertificate", + "aws:iot/domainConfiguration:DomainConfiguration", + "aws:iot/policy:Policy", + "aws:iot/provisioningTemplate:ProvisioningTemplate", + "aws:iot/roleAlias:RoleAlias", + "aws:iot/thingGroup:ThingGroup", + "aws:iot/thingType:ThingType", + "aws:iot/topicRule:TopicRule", + "aws:ivs/channel:Channel", + "aws:ivs/playbackKeyPair:PlaybackKeyPair", + "aws:ivs/recordingConfiguration:RecordingConfiguration", + "aws:ivschat/loggingConfiguration:LoggingConfiguration", + "aws:ivschat/room:Room", + "aws:kendra/dataSource:DataSource", + "aws:kendra/faq:Faq", + "aws:kendra/index:Index", + "aws:kendra/querySuggestionsBlockList:QuerySuggestionsBlockList", + "aws:kendra/thesaurus:Thesaurus", + "aws:keyspaces/keyspace:Keyspace", + "aws:keyspaces/table:Table", + "aws:kinesis/analyticsApplication:AnalyticsApplication", + "aws:kinesis/firehoseDeliveryStream:FirehoseDeliveryStream", + "aws:kinesis/stream:Stream", + "aws:kinesis/videoStream:VideoStream", + "aws:kinesisanalyticsv2/application:Application", + "aws:kms/externalKey:ExternalKey", + "aws:kms/key:Key", + "aws:kms/replicaExternalKey:ReplicaExternalKey", + "aws:kms/replicaKey:ReplicaKey", + "aws:lambda/callbackFunction:CallbackFunction", + "aws:lambda/codeSigningConfig:CodeSigningConfig", + "aws:lambda/eventSourceMapping:EventSourceMapping", + "aws:lambda/function:Function", + "aws:lb/listener:Listener", + "aws:lb/listenerRule:ListenerRule", + "aws:lb/loadBalancer:LoadBalancer", + "aws:lb/targetGroup:TargetGroup", + "aws:lb/trustStore:TrustStore", + "aws:lex/v2modelsBot:V2modelsBot", + "aws:licensemanager/licenseConfiguration:LicenseConfiguration", + "aws:lightsail/bucket:Bucket", + "aws:lightsail/certificate:Certificate", + "aws:lightsail/containerService:ContainerService", + "aws:lightsail/database:Database", + "aws:lightsail/disk:Disk", + "aws:lightsail/distribution:Distribution", + "aws:lightsail/instance:Instance", + "aws:lightsail/keyPair:KeyPair", + "aws:lightsail/lb:Lb", + "aws:location/geofenceCollection:GeofenceCollection", + "aws:location/map:Map", + "aws:location/placeIndex:PlaceIndex", + "aws:location/routeCalculation:RouteCalculation", + "aws:location/tracker:Tracker", + "aws:m2/application:Application", + "aws:m2/environment:Environment", + "aws:macie/customDataIdentifier:CustomDataIdentifier", + "aws:macie/findingsFilter:FindingsFilter", + "aws:macie2/classificationJob:ClassificationJob", + "aws:macie2/member:Member", + "aws:mediaconvert/queue:Queue", + "aws:medialive/channel:Channel", + "aws:medialive/input:Input", + "aws:medialive/inputSecurityGroup:InputSecurityGroup", + "aws:medialive/multiplex:Multiplex", + "aws:mediapackage/channel:Channel", + "aws:mediastore/container:Container", + "aws:memorydb/acl:Acl", + "aws:memorydb/cluster:Cluster", + "aws:memorydb/parameterGroup:ParameterGroup", + "aws:memorydb/snapshot:Snapshot", + "aws:memorydb/subnetGroup:SubnetGroup", + "aws:memorydb/user:User", + "aws:mq/broker:Broker", + "aws:mq/configuration:Configuration", + "aws:msk/cluster:Cluster", + "aws:msk/replicator:Replicator", + "aws:msk/serverlessCluster:ServerlessCluster", + "aws:msk/vpcConnection:VpcConnection", + "aws:mskconnect/connector:Connector", + "aws:mskconnect/customPlugin:CustomPlugin", + "aws:mskconnect/workerConfiguration:WorkerConfiguration", + "aws:mwaa/environment:Environment", + "aws:neptune/cluster:Cluster", + "aws:neptune/clusterEndpoint:ClusterEndpoint", + "aws:neptune/clusterInstance:ClusterInstance", + "aws:neptune/clusterParameterGroup:ClusterParameterGroup", + "aws:neptune/eventSubscription:EventSubscription", + "aws:neptune/parameterGroup:ParameterGroup", + "aws:neptune/subnetGroup:SubnetGroup", + "aws:networkfirewall/firewall:Firewall", + "aws:networkfirewall/firewallPolicy:FirewallPolicy", + "aws:networkfirewall/ruleGroup:RuleGroup", + "aws:networkfirewall/tlsInspectionConfiguration:TlsInspectionConfiguration", + "aws:networkmanager/connectAttachment:ConnectAttachment", + "aws:networkmanager/connectPeer:ConnectPeer", + "aws:networkmanager/connection:Connection", + "aws:networkmanager/coreNetwork:CoreNetwork", + "aws:networkmanager/device:Device", + "aws:networkmanager/globalNetwork:GlobalNetwork", + "aws:networkmanager/link:Link", + "aws:networkmanager/site:Site", + "aws:networkmanager/siteToSiteVpnAttachment:SiteToSiteVpnAttachment", + "aws:networkmanager/transitGatewayPeering:TransitGatewayPeering", + "aws:networkmanager/transitGatewayRouteTableAttachment:TransitGatewayRouteTableAttachment", + "aws:networkmanager/vpcAttachment:VpcAttachment", + "aws:networkmonitor/monitor:Monitor", + "aws:networkmonitor/probe:Probe", + "aws:oam/link:Link", + "aws:oam/sink:Sink", + "aws:opensearch/domain:Domain", + "aws:opensearch/serverlessCollection:ServerlessCollection", + "aws:opensearchingest/pipeline:Pipeline", + "aws:opsworks/customLayer:CustomLayer", + "aws:opsworks/ecsClusterLayer:EcsClusterLayer", + "aws:opsworks/gangliaLayer:GangliaLayer", + "aws:opsworks/haproxyLayer:HaproxyLayer", + "aws:opsworks/javaAppLayer:JavaAppLayer", + "aws:opsworks/memcachedLayer:MemcachedLayer", + "aws:opsworks/mysqlLayer:MysqlLayer", + "aws:opsworks/nodejsAppLayer:NodejsAppLayer", + "aws:opsworks/phpAppLayer:PhpAppLayer", + "aws:opsworks/railsAppLayer:RailsAppLayer", + "aws:opsworks/stack:Stack", + "aws:opsworks/staticWebLayer:StaticWebLayer", + "aws:organizations/account:Account", + "aws:organizations/organizationalUnit:OrganizationalUnit", + "aws:organizations/policy:Policy", + "aws:organizations/resourcePolicy:ResourcePolicy", + "aws:paymentcryptography/key:Key", + "aws:pinpoint/app:App", + "aws:pinpoint/emailTemplate:EmailTemplate", + "aws:pinpoint/smsvoicev2OptOutList:Smsvoicev2OptOutList", + "aws:pinpoint/smsvoicev2PhoneNumber:Smsvoicev2PhoneNumber", + "aws:pipes/pipe:Pipe", + "aws:qldb/ledger:Ledger", + "aws:qldb/stream:Stream", + "aws:quicksight/analysis:Analysis", + "aws:quicksight/dashboard:Dashboard", + "aws:quicksight/dataSet:DataSet", + "aws:quicksight/dataSource:DataSource", + "aws:quicksight/folder:Folder", + "aws:quicksight/namespace:Namespace", + "aws:quicksight/template:Template", + "aws:quicksight/theme:Theme", + "aws:quicksight/vpcConnection:VpcConnection", + "aws:ram/resourceShare:ResourceShare", + "aws:rbin/rule:Rule", + "aws:rds/cluster:Cluster", + "aws:rds/clusterEndpoint:ClusterEndpoint", + "aws:rds/clusterInstance:ClusterInstance", + "aws:rds/clusterParameterGroup:ClusterParameterGroup", + "aws:rds/clusterSnapshot:ClusterSnapshot", + "aws:rds/customDbEngineVersion:CustomDbEngineVersion", + "aws:rds/eventSubscription:EventSubscription", + "aws:rds/instance:Instance", + "aws:rds/integration:Integration", + "aws:rds/optionGroup:OptionGroup", + "aws:rds/parameterGroup:ParameterGroup", + "aws:rds/proxy:Proxy", + "aws:rds/proxyEndpoint:ProxyEndpoint", + "aws:rds/reservedInstance:ReservedInstance", + "aws:rds/snapshot:Snapshot", + "aws:rds/snapshotCopy:SnapshotCopy", + "aws:rds/subnetGroup:SubnetGroup", + "aws:redshift/cluster:Cluster", + "aws:redshift/clusterSnapshot:ClusterSnapshot", + "aws:redshift/eventSubscription:EventSubscription", + "aws:redshift/hsmClientCertificate:HsmClientCertificate", + "aws:redshift/hsmConfiguration:HsmConfiguration", + "aws:redshift/parameterGroup:ParameterGroup", + "aws:redshift/snapshotCopyGrant:SnapshotCopyGrant", + "aws:redshift/snapshotSchedule:SnapshotSchedule", + "aws:redshift/subnetGroup:SubnetGroup", + "aws:redshift/usageLimit:UsageLimit", + "aws:redshiftserverless/namespace:Namespace", + "aws:redshiftserverless/workgroup:Workgroup", + "aws:rekognition/collection:Collection", + "aws:rekognition/streamProcessor:StreamProcessor", + "aws:resourceexplorer/index:Index", + "aws:resourceexplorer/view:View", + "aws:resourcegroups/group:Group", + "aws:rolesanywhere/profile:Profile", + "aws:rolesanywhere/trustAnchor:TrustAnchor", + "aws:route53/healthCheck:HealthCheck", + "aws:route53/resolverEndpoint:ResolverEndpoint", + "aws:route53/resolverFirewallDomainList:ResolverFirewallDomainList", + "aws:route53/resolverFirewallRuleGroup:ResolverFirewallRuleGroup", + "aws:route53/resolverFirewallRuleGroupAssociation:ResolverFirewallRuleGroupAssociation", + "aws:route53/resolverQueryLogConfig:ResolverQueryLogConfig", + "aws:route53/resolverRule:ResolverRule", + "aws:route53/zone:Zone", + "aws:route53domains/registeredDomain:RegisteredDomain", + "aws:route53recoveryreadiness/cell:Cell", + "aws:route53recoveryreadiness/readinessCheck:ReadinessCheck", + "aws:route53recoveryreadiness/recoveryGroup:RecoveryGroup", + "aws:route53recoveryreadiness/resourceSet:ResourceSet", + "aws:rum/appMonitor:AppMonitor", + "aws:s3/bucket:Bucket", + "aws:s3/bucketObject:BucketObject", + "aws:s3/bucketObjectv2:BucketObjectv2", + "aws:s3/bucketV2:BucketV2", + "aws:s3/objectCopy:ObjectCopy", + "aws:s3control/accessGrant:AccessGrant", + "aws:s3control/accessGrantsInstance:AccessGrantsInstance", + "aws:s3control/accessGrantsLocation:AccessGrantsLocation", + "aws:s3control/bucket:Bucket", + "aws:s3control/storageLensConfiguration:StorageLensConfiguration", + "aws:sagemaker/app:App", + "aws:sagemaker/appImageConfig:AppImageConfig", + "aws:sagemaker/codeRepository:CodeRepository", + "aws:sagemaker/dataQualityJobDefinition:DataQualityJobDefinition", + "aws:sagemaker/deviceFleet:DeviceFleet", + "aws:sagemaker/domain:Domain", + "aws:sagemaker/endpoint:Endpoint", + "aws:sagemaker/endpointConfiguration:EndpointConfiguration", + "aws:sagemaker/featureGroup:FeatureGroup", + "aws:sagemaker/flowDefinition:FlowDefinition", + "aws:sagemaker/humanTaskUI:HumanTaskUI", + "aws:sagemaker/image:Image", + "aws:sagemaker/model:Model", + "aws:sagemaker/modelPackageGroup:ModelPackageGroup", + "aws:sagemaker/monitoringSchedule:MonitoringSchedule", + "aws:sagemaker/notebookInstance:NotebookInstance", + "aws:sagemaker/pipeline:Pipeline", + "aws:sagemaker/project:Project", + "aws:sagemaker/space:Space", + "aws:sagemaker/studioLifecycleConfig:StudioLifecycleConfig", + "aws:sagemaker/userProfile:UserProfile", + "aws:sagemaker/workteam:Workteam", + "aws:scheduler/scheduleGroup:ScheduleGroup", + "aws:schemas/discoverer:Discoverer", + "aws:schemas/registry:Registry", + "aws:schemas/schema:Schema", + "aws:secretsmanager/secret:Secret", + "aws:securityhub/automationRule:AutomationRule", + "aws:securitylake/dataLake:DataLake", + "aws:securitylake/subscriber:Subscriber", + "aws:serverlessrepository/cloudFormationStack:CloudFormationStack", + "aws:servicecatalog/portfolio:Portfolio", + "aws:servicecatalog/product:Product", + "aws:servicecatalog/provisionedProduct:ProvisionedProduct", + "aws:servicediscovery/httpNamespace:HttpNamespace", + "aws:servicediscovery/privateDnsNamespace:PrivateDnsNamespace", + "aws:servicediscovery/publicDnsNamespace:PublicDnsNamespace", + "aws:servicediscovery/service:Service", + "aws:sesv2/configurationSet:ConfigurationSet", + "aws:sesv2/contactList:ContactList", + "aws:sesv2/dedicatedIpPool:DedicatedIpPool", + "aws:sesv2/emailIdentity:EmailIdentity", + "aws:sfn/activity:Activity", + "aws:sfn/stateMachine:StateMachine", + "aws:shield/protection:Protection", + "aws:shield/protectionGroup:ProtectionGroup", + "aws:signer/signingProfile:SigningProfile", + "aws:sns/topic:Topic", + "aws:sqs/queue:Queue", + "aws:ssm/activation:Activation", + "aws:ssm/association:Association", + "aws:ssm/contactsRotation:ContactsRotation", + "aws:ssm/document:Document", + "aws:ssm/maintenanceWindow:MaintenanceWindow", + "aws:ssm/parameter:Parameter", + "aws:ssm/patchBaseline:PatchBaseline", + "aws:ssmcontacts/contact:Contact", + "aws:ssmincidents/replicationSet:ReplicationSet", + "aws:ssmincidents/responsePlan:ResponsePlan", + "aws:ssoadmin/application:Application", + "aws:ssoadmin/permissionSet:PermissionSet", + "aws:ssoadmin/trustedTokenIssuer:TrustedTokenIssuer", + "aws:storagegateway/cachesIscsiVolume:CachesIscsiVolume", + "aws:storagegateway/fileSystemAssociation:FileSystemAssociation", + "aws:storagegateway/gateway:Gateway", + "aws:storagegateway/nfsFileShare:NfsFileShare", + "aws:storagegateway/smbFileShare:SmbFileShare", + "aws:storagegateway/storedIscsiVolume:StoredIscsiVolume", + "aws:storagegateway/tapePool:TapePool", + "aws:swf/domain:Domain", + "aws:synthetics/canary:Canary", + "aws:synthetics/group:Group", + "aws:timestreaminfluxdb/dbInstance:DbInstance", + "aws:timestreamwrite/database:Database", + "aws:timestreamwrite/table:Table", + "aws:transcribe/languageModel:LanguageModel", + "aws:transcribe/medicalVocabulary:MedicalVocabulary", + "aws:transcribe/vocabulary:Vocabulary", + "aws:transcribe/vocabularyFilter:VocabularyFilter", + "aws:transfer/agreement:Agreement", + "aws:transfer/certificate:Certificate", + "aws:transfer/connector:Connector", + "aws:transfer/profile:Profile", + "aws:transfer/server:Server", + "aws:transfer/user:User", + "aws:transfer/workflow:Workflow", + "aws:verifiedaccess/endpoint:Endpoint", + "aws:verifiedaccess/group:Group", + "aws:verifiedaccess/instance:Instance", + "aws:verifiedaccess/trustProvider:TrustProvider", + "aws:vpc/securityGroupEgressRule:SecurityGroupEgressRule", + "aws:vpc/securityGroupIngressRule:SecurityGroupIngressRule", + "aws:vpclattice/accessLogSubscription:AccessLogSubscription", + "aws:vpclattice/listener:Listener", + "aws:vpclattice/listenerRule:ListenerRule", + "aws:vpclattice/service:Service", + "aws:vpclattice/serviceNetwork:ServiceNetwork", + "aws:vpclattice/serviceNetworkServiceAssociation:ServiceNetworkServiceAssociation", + "aws:vpclattice/serviceNetworkVpcAssociation:ServiceNetworkVpcAssociation", + "aws:vpclattice/targetGroup:TargetGroup", + "aws:waf/rateBasedRule:RateBasedRule", + "aws:waf/rule:Rule", + "aws:waf/ruleGroup:RuleGroup", + "aws:waf/webAcl:WebAcl", + "aws:wafregional/rateBasedRule:RateBasedRule", + "aws:wafregional/rule:Rule", + "aws:wafregional/ruleGroup:RuleGroup", + "aws:wafregional/webAcl:WebAcl", + "aws:wafv2/ipSet:IpSet", + "aws:wafv2/regexPatternSet:RegexPatternSet", + "aws:wafv2/ruleGroup:RuleGroup", + "aws:wafv2/webAcl:WebAcl", + "aws:workspaces/connectionAlias:ConnectionAlias", + "aws:workspaces/directory:Directory", + "aws:workspaces/ipGroup:IpGroup", + "aws:workspaces/workspace:Workspace", + "aws:xray/group:Group", + "aws:xray/samplingRule:SamplingRule", ] diff --git a/pulumi/modules/aws/types.py b/pulumi/modules/aws/types.py index 2c6415c..c20ea27 100644 --- a/pulumi/modules/aws/types.py +++ b/pulumi/modules/aws/types.py @@ -21,81 +21,132 @@ class IAMUserConfig(BaseModel): """Configuration for an IAM User in AWS.""" + name: str = Field(..., description="Name of the IAM user.") email: str = Field(..., description="Email address of the IAM user.") - groups: List[str] = Field(default_factory=list, description="IAM groups the user belongs to.") - policies: List[str] = Field(default_factory=list, description="IAM policy ARNs attached to the user.") + groups: List[str] = Field( + default_factory=list, description="IAM groups the user belongs to." + ) + policies: List[str] = Field( + default_factory=list, description="IAM policy ARNs attached to the user." + ) class ControlTowerConfig(BaseModel): """Configuration for AWS Control Tower.""" + enabled: bool = Field(default=False, description="Enable AWS Control Tower.") - organizational_unit_name: str = Field(default="LandingZone", description="Name of the Organizational Unit.") - execution_role_name: str = Field(default="AWSControlTowerExecution", description="Name of the execution role.") - execution_role_arn: Optional[str] = Field(None, description="ARN of the execution role.") - admin_role_name: str = Field(default="AWSControlTowerAdmin", description="Name of the admin role.") + organizational_unit_name: str = Field( + default="LandingZone", description="Name of the Organizational Unit." + ) + execution_role_name: str = Field( + default="AWSControlTowerExecution", description="Name of the execution role." + ) + execution_role_arn: Optional[str] = Field( + None, description="ARN of the execution role." + ) + admin_role_name: str = Field( + default="AWSControlTowerAdmin", description="Name of the admin role." + ) admin_role_arn: Optional[str] = Field(None, description="ARN of the admin role.") - audit_role_name: str = Field(default="AWSControlTowerAudit", description="Name of the audit role.") + audit_role_name: str = Field( + default="AWSControlTowerAudit", description="Name of the audit role." + ) audit_role_arn: Optional[str] = Field(None, description="ARN of the audit role.") - log_archive_bucket: Optional[str] = Field(None, description="Name of the log archive bucket.") + log_archive_bucket: Optional[str] = Field( + None, description="Name of the log archive bucket." + ) - @validator('enabled', always=True) + @validator("enabled", always=True) def validate_control_tower_fields(cls, v, values): if v: - required_fields = ['execution_role_arn', 'admin_role_arn'] + required_fields = ["execution_role_arn", "admin_role_arn"] missing = [field for field in required_fields if not values.get(field)] if missing: - raise ValueError(f"Missing fields for Control Tower: {', '.join(missing)}") + raise ValueError( + f"Missing fields for Control Tower: {', '.join(missing)}" + ) return v class TenantAccountConfig(BaseModel): """Configuration for a Tenant Account within AWS.""" + name: str = Field(..., description="Name of the tenant account.") - email: str = Field(..., description="Email address associated with the tenant account.") - administrators: List[str] = Field(default_factory=list, description="Administrators of the tenant account.") - users: List[str] = Field(default_factory=list, description="Users of the tenant account.") - features: List[str] = Field(default_factory=list, description="Enabled features for the tenant account.") - aws: Dict[str, Any] = Field(default_factory=dict, description="AWS-specific configuration for the tenant account.") - tags: Dict[str, str] = Field(default_factory=dict, description="Tags for resources in the tenant account.") + email: str = Field( + ..., description="Email address associated with the tenant account." + ) + administrators: List[str] = Field( + default_factory=list, description="Administrators of the tenant account." + ) + users: List[str] = Field( + default_factory=list, description="Users of the tenant account." + ) + features: List[str] = Field( + default_factory=list, description="Enabled features for the tenant account." + ) + aws: Dict[str, Any] = Field( + default_factory=dict, + description="AWS-specific configuration for the tenant account.", + ) + tags: Dict[str, str] = Field( + default_factory=dict, description="Tags for resources in the tenant account." + ) class GlobalTags(BaseModel): """Global tags to apply to all AWS resources.""" + project: str = Field(default="konductor", description="Project name.") - managed_by: str = Field(default="NASA_SCIP_OPERATIONS", description="Managed by identifier.") + managed_by: str = Field( + default="NASA_SCIP_OPERATIONS", description="Managed by identifier." + ) class AWSConfig(BaseModel): """Aggregated configuration class for AWS module settings.""" + enabled: bool = Field(default=True, description="Enable the AWS module.") profile: str = Field(default="main", description="AWS CLI profile to use.") region: str = Field(default="us-west-2", description="AWS region for deployment.") account_id: str = Field(..., description="AWS account ID.") bucket: str = Field(..., description="Name of the S3 bucket for state storage.") - control_tower: ControlTowerConfig = Field(default_factory=ControlTowerConfig, description="AWS Control Tower configuration.") - iam_users: List[IAMUserConfig] = Field(default_factory=list, description="IAM user configurations.") - landingzones: List[TenantAccountConfig] = Field(default_factory=list, description="Tenant account configurations.") - global_tags: GlobalTags = Field(default_factory=GlobalTags, description="Global tags for all resources.") - compliance: ComplianceConfig = Field(default_factory=ComplianceConfig, description="Compliance configuration.") - version: str = Field(default="0.0.1", description="Version of the local AWS module.") - - @validator('region') + control_tower: ControlTowerConfig = Field( + default_factory=ControlTowerConfig, + description="AWS Control Tower configuration.", + ) + iam_users: List[IAMUserConfig] = Field( + default_factory=list, description="IAM user configurations." + ) + landingzones: List[TenantAccountConfig] = Field( + default_factory=list, description="Tenant account configurations." + ) + global_tags: GlobalTags = Field( + default_factory=GlobalTags, description="Global tags for all resources." + ) + compliance: ComplianceConfig = Field( + default_factory=ComplianceConfig, description="Compliance configuration." + ) + version: str = Field( + default="0.0.1", description="Version of the local AWS module." + ) + + @validator("region") def validate_region(cls, v): - valid_regions = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2'] + valid_regions = ["us-east-1", "us-east-2", "us-west-1", "us-west-2"] if v not in valid_regions: raise ValueError(f"Invalid AWS region: {v}") return v @classmethod - def merge(cls, user_config: Dict[str, Any]) -> 'AWSConfig': + def merge(cls, user_config: Dict[str, Any]) -> "AWSConfig": """Merges user configuration with defaults, handling compliance integration.""" - aws_specific_keys = {k for k in user_config.keys() if k != 'compliance'} - compliance_config = user_config.get('compliance', {}) + aws_specific_keys = {k for k in user_config.keys() if k != "compliance"} + compliance_config = user_config.get("compliance", {}) aws_config = {k: user_config[k] for k in aws_specific_keys} # Build compliance configuration compliance = ComplianceConfig.merge(compliance_config) - aws_config['compliance'] = compliance + aws_config["compliance"] = compliance return cls(**aws_config) diff --git a/pulumi/modules/ceph/deploy.py b/pulumi/modules/ceph/deploy.py index 8fa3e1d..b3d8467 100644 --- a/pulumi/modules/ceph/deploy.py +++ b/pulumi/modules/ceph/deploy.py @@ -3,7 +3,14 @@ from src.lib.namespace import create_namespace from src.lib.helm_chart_versions import get_latest_helm_chart_version -def deploy_rook_operator(name: str, k8s_provider: Provider, kubernetes_distribution: str, project_name: str, namespace: str): + +def deploy_rook_operator( + name: str, + k8s_provider: Provider, + kubernetes_distribution: str, + project_name: str, + namespace: str, +): """ Deploy Ceph Operator using the Helm chart. @@ -33,14 +40,15 @@ def deploy_rook_operator(name: str, k8s_provider: Provider, kubernetes_distribut name, chart="rook-ceph", version=chart_version, - #values=helm_values, + # values=helm_values, values={}, namespace=namespace, repository_opts={"repo": "https://charts.rook.io/release"}, - opts=pulumi.ResourceOptions(provider=k8s_provider) + opts=pulumi.ResourceOptions(provider=k8s_provider), ) - return(release, chart_version) + return (release, chart_version) + def gen_helm_values(kubernetes_distribution: str, project_name: str): """ @@ -57,10 +65,9 @@ def gen_helm_values(kubernetes_distribution: str, project_name: str): Raises: ValueError: If the specified Kubernetes distribution is not supported. """ - common_values = { - } + common_values = {} - if kubernetes_distribution == 'kind': + if kubernetes_distribution == "kind": # Kind-specific Helm values return { **common_values, @@ -69,10 +76,12 @@ def gen_helm_values(kubernetes_distribution: str, project_name: str): }, "logLevel": "INFO", } - elif kubernetes_distribution == 'talos': + elif kubernetes_distribution == "talos": # Talos-specific Helm values per the Talos Docs return { **common_values, } else: - raise ValueError(f"Unsupported Kubernetes distribution: {kubernetes_distribution}") + raise ValueError( + f"Unsupported Kubernetes distribution: {kubernetes_distribution}" + ) diff --git a/pulumi/modules/cert_manager/deploy.py b/pulumi/modules/cert_manager/deploy.py index ae59efd..e6a98e7 100644 --- a/pulumi/modules/cert_manager/deploy.py +++ b/pulumi/modules/cert_manager/deploy.py @@ -23,10 +23,10 @@ def deploy_cert_manager_module( - config_cert_manager: CertManagerConfig, - global_depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[str, k8s.helm.v3.Release, str]: + config_cert_manager: CertManagerConfig, + global_depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[str, k8s.helm.v3.Release, str]: """ Deploys the cert-manager module and returns the version, release resource, and CA certificate. """ @@ -46,10 +46,10 @@ def deploy_cert_manager_module( def deploy_cert_manager( - config_cert_manager: CertManagerConfig, - depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[str, k8s.helm.v3.Release, str]: + config_cert_manager: CertManagerConfig, + depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[str, k8s.helm.v3.Release, str]: """ Deploys cert-manager using Helm and sets up cluster issuers, ensuring that CRDs are available before creating custom resources. @@ -74,7 +74,7 @@ def deploy_cert_manager( chart_repo_url = "https://charts.jetstack.io" # TODO: re-implement into the get_module_config function and adopt across all modules to reduce code duplication - if version == 'latest' or version is None: + if version == "latest" or version is None: version = get_latest_helm_chart_version(chart_repo_url, chart_name) log.info(f"Setting cert-manager chart version to latest: {version}") else: @@ -96,7 +96,9 @@ def deploy_cert_manager( ), opts=pulumi.ResourceOptions( parent=namespace_resource, - custom_timeouts=pulumi.CustomTimeouts(create="8m", update="4m", delete="4m"), + custom_timeouts=pulumi.CustomTimeouts( + create="8m", update="4m", delete="4m" + ), ), k8s_provider=k8s_provider, depends_on=[namespace_resource] + depends_on, @@ -116,13 +118,18 @@ def deploy_cert_manager( ], k8s_provider=k8s_provider, depends_on=[release], - parent=release + parent=release, ) # Create Cluster Issuers using the helper function # TODO: # - make self-signed-issuer configurable enabled/disabled from boolean set in cert_manager/types.py CertManagerConfig class, default to enabled. - cluster_issuer_root, cluster_issuer_ca_certificate, cluster_issuer, ca_secret = create_cluster_issuers( + ( + cluster_issuer_root, + cluster_issuer_ca_certificate, + cluster_issuer, + ca_secret, + ) = create_cluster_issuers( cluster_issuer_name, namespace, release, crds, k8s_provider ) @@ -138,12 +145,13 @@ def deploy_cert_manager( return version, release, ca_data_tls_crt_b64 + def create_cluster_issuers( - cluster_issuer_name: str, - namespace: str, - release: k8s.helm.v3.Release, - crds: List[pulumi.Resource], - k8s_provider: k8s.Provider, + cluster_issuer_name: str, + namespace: str, + release: k8s.helm.v3.Release, + crds: List[pulumi.Resource], + k8s_provider: k8s.Provider, ) -> Tuple[ Optional[k8s.apiextensions.CustomResource], Optional[k8s.apiextensions.CustomResource], @@ -183,7 +191,9 @@ def create_cluster_issuers( parent=release, provider=k8s_provider, depends_on=crds, - custom_timeouts=pulumi.CustomTimeouts(create="5m", update="5m", delete="5m"), + custom_timeouts=pulumi.CustomTimeouts( + create="5m", update="5m", delete="5m" + ), ), ) @@ -215,7 +225,9 @@ def create_cluster_issuers( parent=cluster_issuer_root, provider=k8s_provider, depends_on=[cluster_issuer_root], - custom_timeouts=pulumi.CustomTimeouts(create="5m", update="5m", delete="10m"), + custom_timeouts=pulumi.CustomTimeouts( + create="5m", update="5m", delete="10m" + ), ), ) @@ -236,7 +248,9 @@ def create_cluster_issuers( parent=cluster_issuer_ca_certificate, provider=k8s_provider, depends_on=[cluster_issuer_ca_certificate], - custom_timeouts=pulumi.CustomTimeouts(create="5m", update="5m", delete="5m"), + custom_timeouts=pulumi.CustomTimeouts( + create="5m", update="5m", delete="5m" + ), ), ) @@ -249,12 +263,17 @@ def create_cluster_issuers( parent=cluster_issuer_ca_certificate, provider=k8s_provider, depends_on=[cluster_issuer_ca_certificate], - ) + ), ) else: ca_secret = None - return cluster_issuer_root, cluster_issuer_ca_certificate, cluster_issuer, ca_secret + return ( + cluster_issuer_root, + cluster_issuer_ca_certificate, + cluster_issuer, + ca_secret, + ) except Exception as e: log.error(f"Error during the creation of cluster issuers: {str(e)}") @@ -266,10 +285,10 @@ def generate_helm_values(config_cert_manager: CertManagerConfig) -> Dict[str, An Generates Helm values for the CertManager deployment. """ return { - 'replicaCount': 1, - 'installCRDs': config_cert_manager.install_crds, - 'resources': { - 'limits': {'cpu': '500m', 'memory': '1024Mi'}, - 'requests': {'cpu': '250m', 'memory': '512Mi'}, + "replicaCount": 1, + "installCRDs": config_cert_manager.install_crds, + "resources": { + "limits": {"cpu": "500m", "memory": "1024Mi"}, + "requests": {"cpu": "250m", "memory": "512Mi"}, }, } diff --git a/pulumi/modules/cert_manager/types.py b/pulumi/modules/cert_manager/types.py index c6ebd38..34fb2cc 100644 --- a/pulumi/modules/cert_manager/types.py +++ b/pulumi/modules/cert_manager/types.py @@ -13,6 +13,7 @@ from typing import Optional, Dict, Any import pulumi + @dataclass class CertManagerConfig: version: Optional[str] = "latest" @@ -21,11 +22,13 @@ class CertManagerConfig: install_crds: bool = True @staticmethod - def merge(user_config: Dict[str, Any]) -> 'CertManagerConfig': + def merge(user_config: Dict[str, Any]) -> "CertManagerConfig": default_config = CertManagerConfig() for key, value in user_config.items(): if hasattr(default_config, key): setattr(default_config, key, value) else: - pulumi.log.warn(f"Unknown configuration key '{key}' in cert_manager config.") + pulumi.log.warn( + f"Unknown configuration key '{key}' in cert_manager config." + ) return default_config diff --git a/pulumi/modules/cilium/deploy.py b/pulumi/modules/cilium/deploy.py index 6ae7a0c..5cf6713 100644 --- a/pulumi/modules/cilium/deploy.py +++ b/pulumi/modules/cilium/deploy.py @@ -3,32 +3,38 @@ from pulumi_kubernetes.apiextensions import CustomResource from src.lib.helm_chart_versions import get_latest_helm_chart_version -def deploy_cilium( - name: str, - k8s_provider: k8s.Provider, - kubernetes_distribution: str, - project_name: str, - kubernetes_endpoint_service_address: pulumi.Output[str], - namespace: str, - version: str, - l2_bridge_name: str, - l2announcements: str - ): +def deploy_cilium( + name: str, + k8s_provider: k8s.Provider, + kubernetes_distribution: str, + project_name: str, + kubernetes_endpoint_service_address: pulumi.Output[str], + namespace: str, + version: str, + l2_bridge_name: str, + l2announcements: str, +): # Fetch the latest version of the Cilium Helm chart chart_name = "cilium" - chart_index_url = "https://raw.githubusercontent.com/cilium/charts/master/index.yaml" + chart_index_url = ( + "https://raw.githubusercontent.com/cilium/charts/master/index.yaml" + ) if version is None: # Fetch the latest version of the Cilium Helm chart version = get_latest_helm_chart_version(chart_index_url, chart_name) - pulumi.log.info(f"Setting helm release version to latest: {chart_name}/{version}") + pulumi.log.info( + f"Setting helm release version to latest: {chart_name}/{version}" + ) else: # Log the version override pulumi.log.info(f"Using helm release version: {chart_name}/{version}") # Determine Helm values based on the Kubernetes distribution - helm_values = get_helm_values(kubernetes_distribution, project_name, kubernetes_endpoint_service_address) + helm_values = get_helm_values( + kubernetes_distribution, project_name, kubernetes_endpoint_service_address + ) # Deploy Cilium using the Helm chart release = k8s.helm.v3.Release( @@ -41,11 +47,9 @@ def deploy_cilium( opts=pulumi.ResourceOptions( provider=k8s_provider, custom_timeouts=pulumi.CustomTimeouts( - create="15m", - update="15m", - delete="5m" - ) - ) + create="15m", update="15m", delete="5m" + ), + ), ) cilium_l2_announcement_policy = CustomResource( @@ -57,17 +61,15 @@ def deploy_cilium( "serviceSelector": {"matchLabels": {}}, "interfaces": [l2_bridge_name], "externalIPs": False, - "loadBalancerIPs": True + "loadBalancerIPs": True, }, opts=pulumi.ResourceOptions( parent=release, provider=k8s_provider, custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="8m", - delete="2m" - ) - ) + create="8m", update="8m", delete="2m" + ), + ), ) # Define CiliumLoadBalancerIPPool resource @@ -76,33 +78,27 @@ def deploy_cilium( api_version="cilium.io/v2alpha1", kind="CiliumLoadBalancerIPPool", metadata={"name": "l2-default"}, - spec={ - "cidrs": [{"cidr": l2announcements}] - }, + spec={"cidrs": [{"cidr": l2announcements}]}, opts=pulumi.ResourceOptions( parent=release, provider=k8s_provider, custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="8m", - delete="2m" - ) - ) + create="8m", update="8m", delete="2m" + ), + ), ) return version, release + def get_helm_values( - kubernetes_distribution: str, - project_name: str, - kubernetes_endpoint_service_address: str - ): + kubernetes_distribution: str, + project_name: str, + kubernetes_endpoint_service_address: str, +): # Common Cilium Helm Chart Values common_values = { - "cluster": { - "id": 1, - "name": project_name - }, + "cluster": {"id": 1, "name": project_name}, "routingMode": "tunnel", "tunnelProtocol": "vxlan", "kubeProxyReplacement": "strict", @@ -127,20 +123,17 @@ def get_helm_values( }, } # Kind Kubernetes specific Helm values - if kubernetes_distribution == 'kind': + if kubernetes_distribution == "kind": return { **common_values, "k8sServiceHost": kubernetes_endpoint_service_address, "k8sServicePort": 6443, } - elif kubernetes_distribution == 'talos': + elif kubernetes_distribution == "talos": # Talos-specific Helm values per the Talos Cilium Docs return { **common_values, - "cni": { - "install": True, - "exclusive": False - }, + "cni": {"install": True, "exclusive": False}, "autoDirectNodeRoutes": True, "containerRuntime": {"integration": "containerd"}, "devices": "br+ bond+ thunderbolt+", @@ -148,10 +141,7 @@ def get_helm_values( "endpointRoutes": {"enabled": True}, "bpf": {"masquerade": True}, "localRedirectPolicy": True, - "loadBalancer": { - "algorithm": "maglev", - "mode": "dsr" - }, + "loadBalancer": {"algorithm": "maglev", "mode": "dsr"}, "cgroup": { "autoMount": {"enabled": False}, "hostRoot": "/sys/fs/cgroup", @@ -176,16 +166,24 @@ def get_helm_values( "securityContext": { "capabilities": { "ciliumAgent": [ - "CHOWN", "KILL", "NET_ADMIN", "NET_RAW", "IPC_LOCK", - "SYS_ADMIN", "SYS_RESOURCE", "DAC_OVERRIDE", "FOWNER", - "SETGID", "SETUID" + "CHOWN", + "KILL", + "NET_ADMIN", + "NET_RAW", + "IPC_LOCK", + "SYS_ADMIN", + "SYS_RESOURCE", + "DAC_OVERRIDE", + "FOWNER", + "SETGID", + "SETUID", ], "cleanCiliumState": ["NET_ADMIN", "SYS_ADMIN", "SYS_RESOURCE"], }, }, } - elif kubernetes_distribution == 'kind': + elif kubernetes_distribution == "kind": return { **common_values, "k8sServiceHost": kubernetes_endpoint_ip_string, @@ -195,14 +193,13 @@ def get_helm_values( "routingMode": "tunnel", } else: - raise ValueError(f"Unsupported Kubernetes distribution: {kubernetes_distribution}") + raise ValueError( + f"Unsupported Kubernetes distribution: {kubernetes_distribution}" + ) -# Deploy test loadbalancer service -def deploy_test_service( - namespace: str, - k8s_provider: k8s.Provider - ): +# Deploy test loadbalancer service +def deploy_test_service(namespace: str, k8s_provider: k8s.Provider): # Development only: # nginx pod and loadbalancer service resources are being committed # to test the Cilium L2 announcement policy and LoadBalancerIPPool @@ -213,15 +210,15 @@ def deploy_test_service( namespace=namespace, metadata={"name": "nginx", "labels": {"app": "nginx"}}, spec=k8s.core.v1.PodSpecArgs( - containers=[k8s.core.v1.ContainerArgs( - name="nginx", - image="nginx:latest", - ports=[k8s.core.v1.ContainerPortArgs(container_port=80)] - )] + containers=[ + k8s.core.v1.ContainerArgs( + name="nginx", + image="nginx:latest", + ports=[k8s.core.v1.ContainerPortArgs(container_port=80)], + ) + ] ), - opts=pulumi.ResourceOptions( - provider=k8s_provider - ) + opts=pulumi.ResourceOptions(provider=k8s_provider), ) # Define nginx LoadBalancer Service resource @@ -232,15 +229,11 @@ def deploy_test_service( spec=k8s.core.v1.ServiceSpecArgs( type="LoadBalancer", selector={"app": "nginx"}, - ports=[k8s.core.v1.ServicePortArgs( - protocol="TCP", - port=80, - target_port=80 - )] + ports=[ + k8s.core.v1.ServicePortArgs(protocol="TCP", port=80, target_port=80) + ], ), - opts=pulumi.ResourceOptions( - provider=k8s_provider - ) + opts=pulumi.ResourceOptions(provider=k8s_provider), ) return nginx_pod, nginx_load_balancer_service diff --git a/pulumi/modules/cluster_network_addons/deploy.py b/pulumi/modules/cluster_network_addons/deploy.py index 2d85ee2..6ddcf31 100644 --- a/pulumi/modules/cluster_network_addons/deploy.py +++ b/pulumi/modules/cluster_network_addons/deploy.py @@ -5,12 +5,8 @@ from pulumi_kubernetes.apiextensions.CustomResource import CustomResource from src.lib.namespace import create_namespace -def deploy_cnao( - depends, - version: str, - k8s_provider: k8s.Provider - ): +def deploy_cnao(depends, version: str, k8s_provider: k8s.Provider): # Create namespace ns_name = "cluster-network-addons" ns_retain = True @@ -26,15 +22,15 @@ def deploy_cnao( ns_protect, k8s_provider, custom_labels=ns_labels, - custom_annotations=ns_annotations + custom_annotations=ns_annotations, ) # Fetch the latest stable version of CDI if version is None: - tag_url = 'https://github.com/kubevirt/cluster-network-addons-operator/releases/latest' - tag = requests.get(tag_url, allow_redirects=False).headers.get('location') - version = tag.split('/')[-1] - version = version.lstrip('v') + tag_url = "https://github.com/kubevirt/cluster-network-addons-operator/releases/latest" + tag = requests.get(tag_url, allow_redirects=False).headers.get("location") + version = tag.split("/")[-1] + version = version.lstrip("v") pulumi.log.info(f"Setting helm release version to latest: cnao/{version}") else: # Log the version override @@ -49,11 +45,9 @@ def deploy_cnao( depends_on=depends, provider=k8s_provider, custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="8m", - delete="2m" - ) - ) + create="8m", update="8m", delete="2m" + ), + ), ) operator_manifest_url = f"https://github.com/kubevirt/cluster-network-addons-operator/releases/download/v{version}/operator.yaml" @@ -65,11 +59,9 @@ def deploy_cnao( depends_on=depends, provider=k8s_provider, custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="8m", - delete="2m" - ) - ) + create="8m", update="8m", delete="2m" + ), + ), ) network_addons_config = CustomResource( @@ -84,10 +76,8 @@ def deploy_cnao( depends_on=depends, provider=k8s_provider, custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="8m", - delete="2m" - ) + create="8m", update="8m", delete="2m" + ), ), spec={ "linuxBridge": {}, @@ -97,46 +87,46 @@ def deploy_cnao( "caOverlapInterval": "24h", "certRotateInterval": "24h", "certOverlapInterval": "8h", - } - } + }, + }, ) - #"multus": {}, - #"macvtap": {}, - #"multusDynamicNetworks": {}, - #"kubeSecondaryDNS": {}, - #"kubeMacPool": {}, - #"ovs": {}, - #}, - #"placementConfiguration": { - # "workloads": { - # "nodeSelector": { - # "node-role.kubernetes.io/worker": "", - # } - # }, - # "infra": { - # "affinity": { - # "nodeAffinity": { - # "requiredDuringSchedulingIgnoredDuringExecution": { - # "nodeSelectorTerms": [{ - # "matchExpressions": [{ - # "key": "node-role.kubernetes.io/worker", - # "operator": "Exists", - # }] - # }] - # } - # } - # } - # } - #}, + # "multus": {}, + # "macvtap": {}, + # "multusDynamicNetworks": {}, + # "kubeSecondaryDNS": {}, + # "kubeMacPool": {}, + # "ovs": {}, + # }, + # "placementConfiguration": { + # "workloads": { + # "nodeSelector": { + # "node-role.kubernetes.io/worker": "", + # } + # }, + # "infra": { + # "affinity": { + # "nodeAffinity": { + # "requiredDuringSchedulingIgnoredDuringExecution": { + # "nodeSelectorTerms": [{ + # "matchExpressions": [{ + # "key": "node-role.kubernetes.io/worker", + # "operator": "Exists", + # }] + # }] + # } + # } + # } + # } + # }, return version, nado_operator_resource ## Variable settings for the name and bridge configuration - #network_name = "br0" - #bridge_name = "br0" + # network_name = "br0" + # bridge_name = "br0" ## Pulumi Kubernetes resource for NetworkAttachmentDefinition - #network_attachment_definition = k8s.apiextensions.CustomResource( + # network_attachment_definition = k8s.apiextensions.CustomResource( # "kargo-net-attach-def", # api_version="k8s.cni.cncf.io/v1", # kind="NetworkAttachmentDefinition", @@ -161,7 +151,7 @@ def deploy_cnao( # ] # }}''') # } - #) + # ) ## Export the name of the resource - #pulumi.export('network_attachment_definition_name', network_attachment_definition.metadata['name']) + # pulumi.export('network_attachment_definition_name', network_attachment_definition.metadata['name']) diff --git a/pulumi/modules/containerized_data_importer/deploy.py b/pulumi/modules/containerized_data_importer/deploy.py index e861f42..e824307 100644 --- a/pulumi/modules/containerized_data_importer/deploy.py +++ b/pulumi/modules/containerized_data_importer/deploy.py @@ -15,11 +15,12 @@ from .types import CdiConfig + def deploy_containerized_data_importer_module( - config_cdi: CdiConfig, - global_depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[Optional[str], Optional[pulumi.Resource]]: + config_cdi: CdiConfig, + global_depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[Optional[str], Optional[pulumi.Resource]]: """ Deploys the Containerized Data Importer (CDI) module and returns the version and the deployed resource. @@ -34,7 +35,11 @@ def deploy_containerized_data_importer_module( try: log.info("Starting deployment of CDI module") - version = config_cdi.version if config_cdi.version and config_cdi.version != "latest" else fetch_latest_version() + version = ( + config_cdi.version + if config_cdi.version and config_cdi.version != "latest" + else fetch_latest_version() + ) log.info(f"Using CDI version: {version}") # Create namespace @@ -59,7 +64,7 @@ def deploy_containerized_data_importer_module( opts=pulumi.ResourceOptions( provider=k8s_provider, parent=namespace_resource, - ) + ), ) # Ensure dependencies on operator and namespace @@ -106,8 +111,10 @@ def deploy_containerized_data_importer_module( parent=operator_resource, depends_on=namespace_resource, provider=k8s_provider, - custom_timeouts=pulumi.CustomTimeouts(create="1m", update="1m", delete="1m"), - ) + custom_timeouts=pulumi.CustomTimeouts( + create="1m", update="1m", delete="1m" + ), + ), ) log.info("CDI module deployment complete") @@ -117,6 +124,7 @@ def deploy_containerized_data_importer_module( log.error(f"Deployment of CDI module failed: {str(e)}") raise + # Function to fetch the latest stable semantic version from GitHub releases # TODO: consider making github latest release version fetching a shared utility function & adopting across all modules to reduce code duplication def fetch_latest_version() -> str: @@ -127,10 +135,14 @@ def fetch_latest_version() -> str: str: Latest stable version string. """ try: - latest_release_url = 'https://github.com/kubevirt/containerized-data-importer/releases/latest' - tag = requests.get(latest_release_url, allow_redirects=False).headers.get('location') - version = tag.split('/')[-1] - version = version.lstrip('v') + latest_release_url = ( + "https://github.com/kubevirt/containerized-data-importer/releases/latest" + ) + tag = requests.get(latest_release_url, allow_redirects=False).headers.get( + "location" + ) + version = tag.split("/")[-1] + version = version.lstrip("v") log.info(f"Fetched latest CDI version: {version}") return version except Exception as e: diff --git a/pulumi/modules/containerized_data_importer/types.py b/pulumi/modules/containerized_data_importer/types.py index 1643f37..b013b36 100644 --- a/pulumi/modules/containerized_data_importer/types.py +++ b/pulumi/modules/containerized_data_importer/types.py @@ -7,6 +7,7 @@ from dataclasses import dataclass, field from typing import Optional, Dict, Any + @dataclass class CdiConfig: version: Optional[str] = "latest" @@ -15,7 +16,7 @@ class CdiConfig: annotations: Dict[str, Any] = field(default_factory=dict) @staticmethod - def merge(user_config: Dict[str, Any]) -> 'CdiConfig': + def merge(user_config: Dict[str, Any]) -> "CdiConfig": default_config = CdiConfig() for key, value in user_config.items(): if hasattr(default_config, key): diff --git a/pulumi/modules/hostpath_provisioner/deploy.py b/pulumi/modules/hostpath_provisioner/deploy.py index 5e610e2..bd18fca 100644 --- a/pulumi/modules/hostpath_provisioner/deploy.py +++ b/pulumi/modules/hostpath_provisioner/deploy.py @@ -7,7 +7,11 @@ import pulumi_kubernetes as k8s from pulumi import log -from core.resource_helpers import create_namespace, create_custom_resource, create_config_file +from core.resource_helpers import ( + create_namespace, + create_custom_resource, + create_config_file, +) from core.utils import wait_for_crds from .types import HostPathProvisionerConfig @@ -75,11 +79,17 @@ def deploy_hostpath_provisioner( ) # Determine version to use - version = get_latest_version() if config_hostpath_provisioner.version == "latest" else config_hostpath_provisioner.version + version = ( + get_latest_version() + if config_hostpath_provisioner.version == "latest" + else config_hostpath_provisioner.version + ) # Transformation function to enforce namespace override on all resources # TODO: consider implementing as a utility or resource helper function and adopting directly in core/resource_helpers.py in applicable functions. - def enforce_namespace(resource_args: pulumi.ResourceTransformationArgs) -> pulumi.ResourceTransformationResult: + def enforce_namespace( + resource_args: pulumi.ResourceTransformationArgs, + ) -> pulumi.ResourceTransformationResult: """ Transformation function to enforce namespace on all resources. """ @@ -87,24 +97,24 @@ def enforce_namespace(resource_args: pulumi.ResourceTransformationArgs) -> pulum namespace_conflict = False # Handle ObjectMetaArgs case - if isinstance(props.get('metadata'), k8s.meta.v1.ObjectMetaArgs): - meta = props['metadata'] + if isinstance(props.get("metadata"), k8s.meta.v1.ObjectMetaArgs): + meta = props["metadata"] if meta.namespace and meta.namespace != namespace: namespace_conflict = True updated_meta = k8s.meta.v1.ObjectMetaArgs( name=meta.name, namespace=namespace, labels=meta.labels, - annotations=meta.annotations + annotations=meta.annotations, ) - props['metadata'] = updated_meta + props["metadata"] = updated_meta # Handle dictionary style metadata - elif isinstance(props.get('metadata'), dict): - meta = props['metadata'] - if 'namespace' in meta and meta['namespace'] != namespace: + elif isinstance(props.get("metadata"), dict): + meta = props["metadata"] + if "namespace" in meta and meta["namespace"] != namespace: namespace_conflict = True - meta['namespace'] = namespace + meta["namespace"] = namespace # TODO: document when/if this case is applicable and why this approach is used. if namespace_conflict: @@ -115,7 +125,7 @@ def enforce_namespace(resource_args: pulumi.ResourceTransformationArgs) -> pulum # Deploy the webhook # TODO: consider relocating url variable into the HostpathProvisionerConfig class as a property for better user configuration. # TODO: consider supporting remote and local path webhook.yaml sources. - webhook_url = f'https://github.com/kubevirt/hostpath-provisioner-operator/releases/download/v{version}/webhook.yaml' + webhook_url = f"https://github.com/kubevirt/hostpath-provisioner-operator/releases/download/v{version}/webhook.yaml" webhook = create_config_file( name="hostpath-provisioner-webhook", file=webhook_url, @@ -123,15 +133,17 @@ def enforce_namespace(resource_args: pulumi.ResourceTransformationArgs) -> pulum provider=k8s_provider, parent=namespace_resource, depends_on=depends_on, - custom_timeouts=pulumi.CustomTimeouts(create="10m", update="5m", delete="5m"), - transformations=[enforce_namespace] + custom_timeouts=pulumi.CustomTimeouts( + create="10m", update="5m", delete="5m" + ), + transformations=[enforce_namespace], ), ) # Deploy the operator # TODO: consider relocating url variable into the HostpathProvisionerConfig class as a property for better user configuration. # TODO: consider supporting remote and local path operator.yaml sources. - operator_url = f'https://github.com/kubevirt/hostpath-provisioner-operator/releases/download/v{version}/operator.yaml' + operator_url = f"https://github.com/kubevirt/hostpath-provisioner-operator/releases/download/v{version}/operator.yaml" operator = create_config_file( name="hostpath-provisioner-operator", file=operator_url, @@ -139,8 +151,10 @@ def enforce_namespace(resource_args: pulumi.ResourceTransformationArgs) -> pulum provider=k8s_provider, parent=webhook, depends_on=depends_on, - custom_timeouts=pulumi.CustomTimeouts(create="10m", update="5m", delete="5m"), - transformations=[enforce_namespace] + custom_timeouts=pulumi.CustomTimeouts( + create="10m", update="5m", delete="5m" + ), + transformations=[enforce_namespace], ), ) @@ -165,22 +179,22 @@ def enforce_namespace(resource_args: pulumi.ResourceTransformationArgs) -> pulum }, "spec": { "imagePullPolicy": "IfNotPresent", - "storagePools": [{ - "name": "ssd", - "path": config_hostpath_provisioner.hostpath, - }], - "workload": { - "nodeSelector": { - "kubernetes.io/os": "linux" + "storagePools": [ + { + "name": "ssd", + "path": config_hostpath_provisioner.hostpath, } - } + ], + "workload": {"nodeSelector": {"kubernetes.io/os": "linux"}}, }, }, opts=pulumi.ResourceOptions( parent=operator, depends_on=depends_on + crds, provider=k8s_provider, - custom_timeouts=pulumi.CustomTimeouts(create="10m", update="5m", delete="5m") + custom_timeouts=pulumi.CustomTimeouts( + create="10m", update="5m", delete="5m" + ), ), ) @@ -209,10 +223,12 @@ def get_latest_version() -> str: str: The latest version number. """ try: - tag_url = 'https://github.com/kubevirt/hostpath-provisioner-operator/releases/latest' + tag_url = ( + "https://github.com/kubevirt/hostpath-provisioner-operator/releases/latest" + ) response = requests.get(tag_url, allow_redirects=False) - final_url = response.headers.get('location') - version = final_url.split('/')[-1].lstrip('v') + final_url = response.headers.get("location") + version = final_url.split("/")[-1].lstrip("v") return version except Exception as e: log.error(f"Error fetching the latest version: {e}") @@ -267,6 +283,8 @@ def create_storage_class( opts=pulumi.ResourceOptions( parent=parent, provider=k8s_provider, - custom_timeouts=pulumi.CustomTimeouts(create="5m", update="5m", delete="5m") + custom_timeouts=pulumi.CustomTimeouts( + create="5m", update="5m", delete="5m" + ), ), ) diff --git a/pulumi/modules/hostpath_provisioner/types.py b/pulumi/modules/hostpath_provisioner/types.py index 4761027..5950d71 100644 --- a/pulumi/modules/hostpath_provisioner/types.py +++ b/pulumi/modules/hostpath_provisioner/types.py @@ -8,6 +8,7 @@ from typing import Optional, Dict, Any import pulumi + @dataclass class HostPathProvisionerConfig: version: Optional[str] = "latest" @@ -18,11 +19,13 @@ class HostPathProvisionerConfig: annotations: Dict[str, Any] = field(default_factory=dict) @staticmethod - def merge(user_config: Dict[str, Any]) -> 'HostPathProvisionerConfig': + def merge(user_config: Dict[str, Any]) -> "HostPathProvisionerConfig": default_config = HostPathProvisionerConfig() for key, value in user_config.items(): if hasattr(default_config, key): setattr(default_config, key, value) else: - pulumi.log.warn(f"Unknown configuration key '{key}' in hostpath_provisioner config.") + pulumi.log.warn( + f"Unknown configuration key '{key}' in hostpath_provisioner config." + ) return default_config diff --git a/pulumi/modules/kubernetes_dashboard/deploy.py b/pulumi/modules/kubernetes_dashboard/deploy.py index c304b78..7cca181 100644 --- a/pulumi/modules/kubernetes_dashboard/deploy.py +++ b/pulumi/modules/kubernetes_dashboard/deploy.py @@ -3,28 +3,20 @@ from src.lib.namespace import create_namespace from src.lib.helm_chart_versions import get_latest_helm_chart_version -def deploy_kubernetes_dashboard( - depends: pulumi.Input[list], - ns_name: str, - version: str, - k8s_provider: k8s.Provider, - ): +def deploy_kubernetes_dashboard( + depends: pulumi.Input[list], + ns_name: str, + version: str, + k8s_provider: k8s.Provider, +): # Create namespace ns_retain = True ns_protect = False ns_annotations = {} - ns_labels = { - "kubernetes.io/metadata.name": ns_name - } + ns_labels = {"kubernetes.io/metadata.name": ns_name} namespace = create_namespace( - depends, - ns_name, - ns_retain, - ns_protect, - k8s_provider, - ns_labels, - ns_annotations + depends, ns_name, ns_retain, ns_protect, k8s_provider, ns_labels, ns_annotations ) # Fetch the latest version from the helm chart index @@ -36,32 +28,30 @@ def deploy_kubernetes_dashboard( # Fetch the latest version from the helm chart index if version is not set if version is None: version = get_latest_helm_chart_version(chart_index_url, chart_name) - pulumi.log.info(f"Setting helm release version to latest stable: {chart_name}/{version}") + pulumi.log.info( + f"Setting helm release version to latest stable: {chart_name}/{version}" + ) else: # Log the version override pulumi.log.info(f"Using helm release version: {chart_name}/{version}") release = k8s.helm.v3.Release( - "kubernetes-dashboard", - k8s.helm.v3.ReleaseArgs( - chart=chart_name, - version=version, - namespace=ns_name, - skip_await=False, - repository_opts= k8s.helm.v3.RepositoryOptsArgs( - repo=chart_url - ), + "kubernetes-dashboard", + k8s.helm.v3.ReleaseArgs( + chart=chart_name, + version=version, + namespace=ns_name, + skip_await=False, + repository_opts=k8s.helm.v3.RepositoryOptsArgs(repo=chart_url), + ), + opts=pulumi.ResourceOptions( + provider=k8s_provider, + parent=namespace, + depends_on=[namespace], + custom_timeouts=pulumi.CustomTimeouts( + create="8m", update="10m", delete="10m" ), - opts=pulumi.ResourceOptions( - provider = k8s_provider, - parent=namespace, - depends_on=[namespace], - custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="10m", - delete="10m" - ) - ) - ) + ), + ) return version, release diff --git a/pulumi/modules/kubevirt/deploy.py b/pulumi/modules/kubevirt/deploy.py index af3e34b..e5234c1 100644 --- a/pulumi/modules/kubevirt/deploy.py +++ b/pulumi/modules/kubevirt/deploy.py @@ -42,11 +42,12 @@ ) from .types import KubeVirtConfig + def deploy_kubevirt_module( - config_kubevirt: KubeVirtConfig, - global_depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[Optional[str], k8s.apiextensions.CustomResource]: + config_kubevirt: KubeVirtConfig, + global_depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[Optional[str], k8s.apiextensions.CustomResource]: """ Deploys the KubeVirt module and returns the version and the deployed resource. """ @@ -65,11 +66,12 @@ def deploy_kubevirt_module( return kubevirt_version, kubevirt_resource + def deploy_kubevirt( - config_kubevirt: KubeVirtConfig, - depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[str, Optional[pulumi.Resource]]: + config_kubevirt: KubeVirtConfig, + depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[str, Optional[pulumi.Resource]]: """ Deploys KubeVirt operator and creates the KubeVirt CustomResource, ensuring that the CRD is available before creating the CustomResource. @@ -93,7 +95,7 @@ def deploy_kubevirt( # Determine latest version release from GitHub Releases # TODO: reimplement into the get_module_config function and adopt across all modules to reduce code duplication - if version == 'latest' or version is None: + if version == "latest" or version is None: version = get_latest_kubevirt_version() log.info(f"Setting KubeVirt release version to latest: {version}") else: @@ -104,7 +106,7 @@ def deploy_kubevirt( transformed_yaml = _transform_yaml(kubevirt_operator_yaml, namespace) # Write transformed YAML to a temporary file - with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp_file: + with tempfile.NamedTemporaryFile(delete=False, mode="w") as temp_file: yaml.dump_all(transformed_yaml, temp_file) temp_file_path = temp_file.name @@ -112,7 +114,7 @@ def deploy_kubevirt( try: # Deploy KubeVirt operator using the helper function operator = create_config_file( - name='kubevirt-operator', + name="kubevirt-operator", file=temp_file_path, opts=pulumi.ResourceOptions( parent=namespace_resource, @@ -136,7 +138,7 @@ def deploy_kubevirt( ], k8s_provider=k8s_provider, depends_on=depends_on, - parent=operator + parent=operator, ) # Create the KubeVirt resource always @@ -191,12 +193,13 @@ def get_latest_kubevirt_version() -> str: """ # TODO: relocate this URL to a default in the KubevirtConfig class and allow for an override - url = 'https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt' + url = "https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt" response = requests.get(url) if response.status_code != 200: raise Exception(f"Failed to fetch latest KubeVirt version from {url}") return response.text.strip().lstrip("v") + def download_kubevirt_operator_yaml(version: str) -> Any: """ Downloads the KubeVirt operator YAML for the specified version. @@ -204,12 +207,13 @@ def download_kubevirt_operator_yaml(version: str) -> Any: # TODO: relocate this URL to a default in the KubevirtConfig class and allow for an override # TODO: support remote or local kubevirt-operator.yaml file - url = f'https://github.com/kubevirt/kubevirt/releases/download/v{version}/kubevirt-operator.yaml' + url = f"https://github.com/kubevirt/kubevirt/releases/download/v{version}/kubevirt-operator.yaml" response = requests.get(url) if response.status_code != 200: raise Exception(f"Failed to download KubeVirt operator YAML from {url}") return list(yaml.safe_load_all(response.text)) + # Function to remove Namespace resources from the YAML data and replace other object namespaces with the specified namespace value as an override def _transform_yaml(yaml_data: Any, namespace: str) -> List[Dict[str, Any]]: """ @@ -217,9 +221,9 @@ def _transform_yaml(yaml_data: Any, namespace: str) -> List[Dict[str, Any]]: """ transformed = [] for resource in yaml_data: - if resource.get('kind') == 'Namespace': + if resource.get("kind") == "Namespace": continue - if 'metadata' in resource: - resource['metadata']['namespace'] = namespace + if "metadata" in resource: + resource["metadata"]["namespace"] = namespace transformed.append(resource) return transformed diff --git a/pulumi/modules/kubevirt/types.py b/pulumi/modules/kubevirt/types.py index a3fc64f..f23a010 100644 --- a/pulumi/modules/kubevirt/types.py +++ b/pulumi/modules/kubevirt/types.py @@ -23,6 +23,7 @@ import pulumi from core.metadata import get_global_labels, get_global_annotations + @dataclass class KubeVirtConfig: namespace: str = "kubevirt" @@ -32,7 +33,7 @@ class KubeVirtConfig: annotations: Dict[str, Any] = field(default_factory=dict) @classmethod - def merge(cls, user_config: Dict[str, Any]) -> 'KubeVirtConfig': + def merge(cls, user_config: Dict[str, Any]) -> "KubeVirtConfig": default_config = cls() merged_config = default_config.__dict__.copy() @@ -40,12 +41,14 @@ def merge(cls, user_config: Dict[str, Any]) -> 'KubeVirtConfig': if hasattr(default_config, key): merged_config[key] = value else: - pulumi.log.warn(f"Unknown configuration key '{key}' in kubevirt config.") + pulumi.log.warn( + f"Unknown configuration key '{key}' in kubevirt config." + ) global_labels = get_global_labels() global_annotations = get_global_annotations() - merged_config['labels'].update(global_labels) - merged_config['annotations'].update(global_annotations) + merged_config["labels"].update(global_labels) + merged_config["annotations"].update(global_annotations) return cls(**merged_config) diff --git a/pulumi/modules/kv_manager/deploy.py b/pulumi/modules/kv_manager/deploy.py index 9956529..7ce7a8b 100644 --- a/pulumi/modules/kv_manager/deploy.py +++ b/pulumi/modules/kv_manager/deploy.py @@ -8,11 +8,18 @@ from kubernetes.client import api_client - -def deploy_ui_for_kubevirt(name: str, k8s_provider: Provider, kubernetes_distribution: str, project_name: str, namespace: str): +def deploy_ui_for_kubevirt( + name: str, + k8s_provider: Provider, + kubernetes_distribution: str, + project_name: str, + namespace: str, +): # Initialize Pulumi configuration pconfig = pulumi.Config() # There's no helm chart for kubevirt-manager so - kubevirt_manager_manifest_url = 'https://raw.githubusercontent.com/kubevirt-manager/kubevirt-manager/main/kubernetes/bundled.yaml' - k8s_yaml = k8s.yaml.ConfigFile("kubevirt-manager", file=kubevirt_manager_manifest_url) + kubevirt_manager_manifest_url = "https://raw.githubusercontent.com/kubevirt-manager/kubevirt-manager/main/kubernetes/bundled.yaml" + k8s_yaml = k8s.yaml.ConfigFile( + "kubevirt-manager", file=kubevirt_manager_manifest_url + ) diff --git a/pulumi/modules/local_path_storage/deploy.py b/pulumi/modules/local_path_storage/deploy.py index 94a3222..b1ba6d1 100644 --- a/pulumi/modules/local_path_storage/deploy.py +++ b/pulumi/modules/local_path_storage/deploy.py @@ -1,15 +1,23 @@ import pulumi import pulumi_kubernetes as k8s -def deploy_local_path_storage(k8s_provider: k8s.Provider, namespace: str, default_path: str): + +def deploy_local_path_storage( + k8s_provider: k8s.Provider, namespace: str, default_path: str +): # Rancher local-path-provisioner URL url_local_path_provisioner = "https://github.com/rancher/local-path-provisioner/raw/master/deploy/local-path-storage.yaml" # Define a transformation function to modify the ConfigMap's config.json def configmap_transformation(obj): - if obj["kind"] == "ConfigMap" and obj["metadata"]["name"] == "local-path-config": + if ( + obj["kind"] == "ConfigMap" + and obj["metadata"]["name"] == "local-path-config" + ): # Using an f-string to dynamically insert the value of default_path - obj["data"]["config.json"] = f"""{{ + obj["data"][ + "config.json" + ] = f"""{{ "nodePathMap":[{{ "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths":[ @@ -27,21 +35,20 @@ def storageclass_transformation(obj): # Add the annotation to make this the default storage class if "annotations" not in obj["metadata"]: obj["metadata"]["annotations"] = {} - obj["metadata"]["annotations"]["storageclass.kubernetes.io/is-default-class"] = "true" + obj["metadata"]["annotations"][ + "storageclass.kubernetes.io/is-default-class" + ] = "true" return obj # Deploy local-path-provisioner using YAML configuration rancher_local_path_provisioner = k8s.yaml.ConfigFile( "rancherLocalPathProvisioner", file=url_local_path_provisioner, - transformations=[ - configmap_transformation, - storageclass_transformation - ], - opts=pulumi.ResourceOptions(provider=k8s_provider) + transformations=[configmap_transformation, storageclass_transformation], + opts=pulumi.ResourceOptions(provider=k8s_provider), ) # Export the storage class name pulumi.export("modified_config_map_namespace", namespace) - return(rancher_local_path_provisioner) + return rancher_local_path_provisioner diff --git a/pulumi/modules/multus/deploy.py b/pulumi/modules/multus/deploy.py index b34f2a7..de54fba 100644 --- a/pulumi/modules/multus/deploy.py +++ b/pulumi/modules/multus/deploy.py @@ -16,11 +16,12 @@ from .types import MultusConfig + def deploy_multus_module( - config_multus: MultusConfig, - global_depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[str, Optional[pulumi.Resource]]: + config_multus: MultusConfig, + global_depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[str, Optional[pulumi.Resource]]: """ Deploys the Multus module and returns the version and the deployed resource. """ @@ -35,11 +36,12 @@ def deploy_multus_module( return multus_version, multus_resource + def deploy_multus( - config_multus: MultusConfig, - depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[str, Optional[pulumi.Resource]]: + config_multus: MultusConfig, + depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[str, Optional[pulumi.Resource]]: """ Deploys Multus using YAML manifest and creates a NetworkAttachmentDefinition, ensuring proper paths for host mounts. @@ -66,11 +68,9 @@ def deploy_multus( parent=namespace_resource, transformations=[transform_host_path], custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="8m", - delete="2m" - ) - ) + create="8m", update="8m", delete="2m" + ), + ), ) # Create NetworkAttachmentDefinition @@ -85,7 +85,10 @@ def deploy_multus( "namespace": config_multus.namespace, }, "spec": { - "config": pulumi.Output.all(config_multus.bridge_name, config_multus.bridge_name).apply(lambda args: f''' + "config": pulumi.Output.all( + config_multus.bridge_name, config_multus.bridge_name + ).apply( + lambda args: f""" {{ "cniVersion": "0.3.1", "name": "{args[0]}", @@ -99,7 +102,8 @@ def deploy_multus( "type": "tuning" }} ] - }}''') + }}""" + ) }, }, opts=pulumi.ResourceOptions( @@ -114,11 +118,16 @@ def deploy_multus( ), ) - pulumi.export('network_attachment_definition', network_attachment_definition.metadata['name']) + pulumi.export( + "network_attachment_definition", network_attachment_definition.metadata["name"] + ) return version, multus -def transform_host_path(args: pulumi.ResourceTransformationArgs) -> pulumi.ResourceTransformationResult: + +def transform_host_path( + args: pulumi.ResourceTransformationArgs, +) -> pulumi.ResourceTransformationResult: """ Transforms the host paths in the Multus DaemonSet. @@ -127,20 +136,23 @@ def transform_host_path(args: pulumi.ResourceTransformationArgs) -> pulumi.Resou """ obj = args.props - if obj.get('kind', '') == 'DaemonSet' and obj.get('metadata', {}).get('name', '') == 'kube-multus-ds': - containers = obj['spec']['template']['spec'].get('containers', []) + if ( + obj.get("kind", "") == "DaemonSet" + and obj.get("metadata", {}).get("name", "") == "kube-multus-ds" + ): + containers = obj["spec"]["template"]["spec"].get("containers", []) for container in containers: - volume_mounts = container.get('volumeMounts', []) + volume_mounts = container.get("volumeMounts", []) for vm in volume_mounts: - current_path = vm.get('mountPath', '').rstrip('/') - if current_path == '/run/netns': - vm['mountPath'] = '/var/run/netns' + current_path = vm.get("mountPath", "").rstrip("/") + if current_path == "/run/netns": + vm["mountPath"] = "/var/run/netns" - volumes = obj['spec']['template']['spec'].get('volumes', []) + volumes = obj["spec"]["template"]["spec"].get("volumes", []) for vol in volumes: - if 'hostPath' in vol: - current_path = vol['hostPath'].get('path', '').rstrip('/') - if current_path == '/run/netns': - vol['hostPath']['path'] = '/var/run/netns' + if "hostPath" in vol: + current_path = vol["hostPath"].get("path", "").rstrip("/") + if current_path == "/run/netns": + vol["hostPath"]["path"] = "/var/run/netns" return pulumi.ResourceTransformationResult(props=obj, opts=args.opts) diff --git a/pulumi/modules/multus/types.py b/pulumi/modules/multus/types.py index 413d3e1..3fc5620 100644 --- a/pulumi/modules/multus/types.py +++ b/pulumi/modules/multus/types.py @@ -8,6 +8,7 @@ from typing import Optional, Dict, Any import pulumi + @dataclass class MultusConfig: version: str = "master" @@ -17,7 +18,7 @@ class MultusConfig: annotations: Dict[str, Any] = field(default_factory=dict) @staticmethod - def merge(user_config: Dict[str, Any]) -> 'MultusConfig': + def merge(user_config: Dict[str, Any]) -> "MultusConfig": default_config = MultusConfig() for key, value in user_config.items(): if hasattr(default_config, key): diff --git a/pulumi/modules/openunison/deploy.py b/pulumi/modules/openunison/deploy.py index 6aed38a..acba0e8 100644 --- a/pulumi/modules/openunison/deploy.py +++ b/pulumi/modules/openunison/deploy.py @@ -7,42 +7,35 @@ from src.lib.namespace import create_namespace from src.lib.helm_chart_versions import get_latest_helm_chart_version + def sanitize_name(name: str) -> str: """Ensure the name complies with DNS-1035 and RFC 1123.""" - name = name.strip('-') + name = name.strip("-") if not name: raise ValueError("Invalid name: resulting sanitized name is empty") return name -def deploy_openunison( - depends, - ns_name: str, - version: str, - k8s_provider: k8s.Provider, - domain_suffix: str, - cluster_issuer: str, - cert_manager_selfsigned_cert: str, - kubernetes_dashboard_release: str, - ou_github_client_id: str, - ou_github_client_secret: str, - ou_github_teams: str, - enabled - ): +def deploy_openunison( + depends, + ns_name: str, + version: str, + k8s_provider: k8s.Provider, + domain_suffix: str, + cluster_issuer: str, + cert_manager_selfsigned_cert: str, + kubernetes_dashboard_release: str, + ou_github_client_id: str, + ou_github_client_secret: str, + ou_github_teams: str, + enabled, +): ns_retain = True ns_protect = False ns_annotations = {} - ns_labels = { - "kubernetes.io/metadata.name": ns_name - } + ns_labels = {"kubernetes.io/metadata.name": ns_name} namespace = create_namespace( - depends, - ns_name, - ns_retain, - ns_protect, - k8s_provider, - ns_labels, - ns_annotations + depends, ns_name, ns_retain, ns_protect, k8s_provider, ns_labels, ns_annotations ) ou_certificate = CustomResource( @@ -57,10 +50,7 @@ def deploy_openunison( "secretName": "ou-tls-certificate", "commonName": domain_suffix, "isCA": False, - "dnsNames": [ - domain_suffix, - f"*.{domain_suffix}" - ], + "dnsNames": [domain_suffix, f"*.{domain_suffix}"], "issuerRef": { "name": cluster_issuer, "kind": "ClusterIssuer", @@ -71,21 +61,16 @@ def deploy_openunison( "encoding": "PKCS1", "size": 2048, }, - "usages": [ - "server auth", - "client auth" - ] + "usages": ["server auth", "client auth"], }, opts=pulumi.ResourceOptions( provider=k8s_provider, parent=namespace, depends_on=depends, custom_timeouts=pulumi.CustomTimeouts( - create="5m", - update="10m", - delete="10m" - ) - ) + create="5m", update="10m", delete="10m" + ), + ), ) depends.append(ou_certificate) @@ -100,35 +85,30 @@ def deploy_openunison( "force_redirect_to_tls": False, "createIngressCertificate": False, "ingress_type": "nginx", - "ingress_annotations": {} + "ingress_annotations": {}, }, "cert_template": { "ou": "Kubernetes", "o": "MyOrg", "l": "My Cluster", "st": "State of Cluster", - "c": "MyCountry" + "c": "MyCountry", }, "myvd_config_path": "WEB-INF/myvd.conf", "k8s_cluster_name": "openunison-kargo", "enable_impersonation": True, - "impersonation": { - "use_jetstack": True, - "explicit_certificate_trust": True - }, + "impersonation": {"use_jetstack": True, "explicit_certificate_trust": True}, "dashboard": { "namespace": "kubernetes-dashboard", "label": "app.kubernetes.io/name=kubernetes-dashboard", - "require_session": True - }, - "certs": { - "use_k8s_cm": False + "require_session": True, }, + "certs": {"use_k8s_cm": False}, "trusted_certs": [ - { - "name": "unison-ca", - "pem_b64": cert_manager_selfsigned_cert, - } + { + "name": "unison-ca", + "pem_b64": cert_manager_selfsigned_cert, + } ], "monitoring": { "prometheus_service_account": "system:serviceaccount:monitoring:prometheus-k8s" @@ -138,67 +118,60 @@ def deploy_openunison( "teams": ou_github_teams, }, "network_policies": { - "enabled": False, - "ingress": { - "enabled": True, - "labels": { - "app.kubernetes.io/name": "ingress-nginx" - } - }, - "monitoring": { - "enabled": True, - "labels": { - "app.kubernetes.io/name": "monitoring" - } - }, - "apiserver": { "enabled": False, - "labels": { - "app.kubernetes.io/name": "kube-system" - } - } + "ingress": { + "enabled": True, + "labels": {"app.kubernetes.io/name": "ingress-nginx"}, + }, + "monitoring": { + "enabled": True, + "labels": {"app.kubernetes.io/name": "monitoring"}, + }, + "apiserver": { + "enabled": False, + "labels": {"app.kubernetes.io/name": "kube-system"}, + }, }, "services": { "enable_tokenrequest": False, "token_request_audience": "api", "token_request_expiration_seconds": 600, - "node_selectors": [] + "node_selectors": [], }, "openunison": { - "replicas": 1, - "non_secret_data": { - "K8S_DB_SSO": "oidc", - "PROMETHEUS_SERVICE_ACCOUNT": "system:serviceaccount:monitoring:prometheus-k8s", - "SHOW_PORTAL_ORGS": "False" + "replicas": 1, + "non_secret_data": { + "K8S_DB_SSO": "oidc", + "PROMETHEUS_SERVICE_ACCOUNT": "system:serviceaccount:monitoring:prometheus-k8s", + "SHOW_PORTAL_ORGS": "False", + }, + "secrets": [], + "enable_provisioning": False, + "use_standard_jit_workflow": True, + "apps": [], }, - "secrets": [], - "enable_provisioning": False, - "use_standard_jit_workflow": True, - "apps":[], - } } # now that OpenUnison is deployed, we'll make ClusterAdmins of all the groups specified in openunison.github.teams - github_teams = ou_github_teams.split(',') + github_teams = ou_github_teams.split(",") subjects = [] az_groups = [] for team in github_teams: team = team.strip() - if team.endswith('/'): + if team.endswith("/"): team = team[:-1] team = sanitize_name(team) subject = k8s.rbac.v1.SubjectArgs( - kind="Group", - api_group="rbac.authorization.k8s.io", - name=team + kind="Group", api_group="rbac.authorization.k8s.io", name=team ) subjects.append(subject) az_groups.append(team) # Retrieve encoded icon assets from src.openunison.encoded_assets import return_encoded_assets + assets = return_encoded_assets() # Define the icons for the apps as json serializable base64 encoded strings @@ -236,16 +209,16 @@ def deploy_openunison( ) ou_helm_values["openunison"]["apps"].append( - { - "name": "alertmanager", - "label": "Alert Manager", - "org": "b1bf4c92-7220-4ad2-91af-ee0fe0af7312", - "badgeUrl": "https://alertmanager." + domain_suffix + "/", - "injectToken": False, - "proxyTo": "http://alertmanager.monitoring.svc:9093${fullURI}", - "az_groups": az_groups, - "icon": f"{alertmanager_icon_json}", - } + { + "name": "alertmanager", + "label": "Alert Manager", + "org": "b1bf4c92-7220-4ad2-91af-ee0fe0af7312", + "badgeUrl": "https://alertmanager." + domain_suffix + "/", + "injectToken": False, + "proxyTo": "http://alertmanager.monitoring.svc:9093${fullURI}", + "az_groups": az_groups, + "icon": f"{alertmanager_icon_json}", + } ) ou_helm_values["openunison"]["apps"].append( @@ -255,21 +228,27 @@ def deploy_openunison( "org": "b1bf4c92-7220-4ad2-91af-ee0fe0af7312", "badgeUrl": "https://grafana." + domain_suffix + "/", "injectToken": False, - "azSuccessResponse":"grafana", + "azSuccessResponse": "grafana", "proxyTo": "http://grafana.monitoring.svc${fullURI}", "az_groups": az_groups, "icon": f"{grafana_icon_json}", } ) - ou_helm_values["dashboard"]["service_name"] = kubernetes_dashboard_release.name.apply(lambda name: sanitize_name(name)) - ou_helm_values["dashboard"]["cert_name"] = kubernetes_dashboard_release.name.apply(lambda name: sanitize_name(name + "-certs")) + ou_helm_values["dashboard"][ + "service_name" + ] = kubernetes_dashboard_release.name.apply(lambda name: sanitize_name(name)) + ou_helm_values["dashboard"]["cert_name"] = kubernetes_dashboard_release.name.apply( + lambda name: sanitize_name(name + "-certs") + ) # Apply function to wait for the dashboard release names before proceeding def wait_for_dashboard_release_names(): return ou_helm_values - orchesrta_login_portal_helm_values = kubernetes_dashboard_release.name.apply(lambda _: wait_for_dashboard_release_names()) + orchesrta_login_portal_helm_values = kubernetes_dashboard_release.name.apply( + lambda _: wait_for_dashboard_release_names() + ) # Fetch the latest version from the helm chart index chart_name = "openunison-operator" @@ -278,75 +257,72 @@ def wait_for_dashboard_release_names(): chart_index_url = f"{chart_url}/{chart_index_path}" if version is None: version = get_latest_helm_chart_version(chart_index_url, chart_name) - pulumi.log.info(f"Setting helm release version to latest: {chart_name}/{version}") + pulumi.log.info( + f"Setting helm release version to latest: {chart_name}/{version}" + ) else: pulumi.log.info(f"Using helm release version: {chart_name}/{version}") # Create Helm release operator_release = k8s.helm.v3.Release( - 'openunison-operator', + "openunison-operator", k8s.helm.v3.ReleaseArgs( chart=chart_name, version=version, values=orchesrta_login_portal_helm_values, namespace=ns_name, skip_await=False, - repository_opts=k8s.helm.v3.RepositoryOptsArgs( - repo=chart_url - ), + repository_opts=k8s.helm.v3.RepositoryOptsArgs(repo=chart_url), ), opts=pulumi.ResourceOptions( provider=k8s_provider, parent=namespace, depends_on=depends, custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="10m", - delete="10m" - ) - ) + create="8m", update="10m", delete="10m" + ), + ), ) raw_secret_data = { "K8S_DB_SECRET": secrets.token_urlsafe(64), "unisonKeystorePassword": secrets.token_urlsafe(64), - "GITHUB_SECRET_ID": ou_github_client_secret + "GITHUB_SECRET_ID": ou_github_client_secret, } encoded_secret_data = { - key: base64.b64encode(value.encode('utf-8')).decode('utf-8') - for key, value in raw_secret_data.items() + key: base64.b64encode(value.encode("utf-8")).decode("utf-8") + for key, value in raw_secret_data.items() } # Base64 encode the GitHub client secret orchestra_secret_source = k8s.core.v1.Secret( "orchestra-secrets-source", - metadata= k8s.meta.v1.ObjectMetaArgs( - name="orchestra-secrets-source", - namespace=ns_name + metadata=k8s.meta.v1.ObjectMetaArgs( + name="orchestra-secrets-source", namespace=ns_name ), data={ - "K8S_DB_SECRET": encoded_secret_data['K8S_DB_SECRET'], + "K8S_DB_SECRET": encoded_secret_data["K8S_DB_SECRET"], "unisonKeystorePassword": encoded_secret_data["unisonKeystorePassword"], - "GITHUB_SECRET_ID": encoded_secret_data["GITHUB_SECRET_ID"] + "GITHUB_SECRET_ID": encoded_secret_data["GITHUB_SECRET_ID"], }, opts=pulumi.ResourceOptions( parent=operator_release, - provider = k8s_provider, + provider=k8s_provider, retain_on_delete=False, delete_before_replace=True, custom_timeouts=pulumi.CustomTimeouts( - create="10m", - update="10m", - delete="10m" - ) - ) + create="10m", update="10m", delete="10m" + ), + ), ) - orchestra_chart_name = 'orchestra' - orchestra_chart_version = get_latest_helm_chart_version(chart_index_url, orchestra_chart_name) + orchestra_chart_name = "orchestra" + orchestra_chart_version = get_latest_helm_chart_version( + chart_index_url, orchestra_chart_name + ) ou_orchestra_release = k8s.helm.v3.Release( - 'orchestra', + "orchestra", k8s.helm.v3.ReleaseArgs( chart=orchestra_chart_name, version=orchestra_chart_version, @@ -354,20 +330,16 @@ def wait_for_dashboard_release_names(): namespace=ns_name, skip_await=False, wait_for_jobs=True, - repository_opts= k8s.helm.v3.RepositoryOptsArgs( - repo=chart_url - ), + repository_opts=k8s.helm.v3.RepositoryOptsArgs(repo=chart_url), ), opts=pulumi.ResourceOptions( parent=operator_release, depends_on=[operator_release, orchestra_secret_source], - provider = k8s_provider, + provider=k8s_provider, custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="10m", - delete="10m" - ) - ) + create="8m", update="10m", delete="10m" + ), + ), ) ou_orchestra_release_name = ou_orchestra_release.name.apply(lambda name: name) @@ -377,17 +349,19 @@ def update_values(name): **ou_helm_values, "impersonation": { **ou_helm_values["impersonation"], - "orchestra_release_name": name - } + "orchestra_release_name": name, + }, } # Apply the updated values updated_values = ou_orchestra_release_name.apply(update_values) - orchestra_login_portal_chart_name = 'orchestra-login-portal' - orchestra_login_portal_chart_version = get_latest_helm_chart_version(chart_index_url,orchestra_login_portal_chart_name) + orchestra_login_portal_chart_name = "orchestra-login-portal" + orchestra_login_portal_chart_version = get_latest_helm_chart_version( + chart_index_url, orchestra_login_portal_chart_name + ) ou_orchestra_login_portal_release = k8s.helm.v3.Release( - 'orchestra-login-portal', + "orchestra-login-portal", k8s.helm.v3.ReleaseArgs( chart=orchestra_login_portal_chart_name, version=orchestra_login_portal_chart_version, @@ -395,27 +369,25 @@ def update_values(name): namespace=ns_name, skip_await=False, wait_for_jobs=True, - repository_opts= k8s.helm.v3.RepositoryOptsArgs( - repo=chart_url - ), + repository_opts=k8s.helm.v3.RepositoryOptsArgs(repo=chart_url), ), opts=pulumi.ResourceOptions( - provider = k8s_provider, + provider=k8s_provider, parent=ou_orchestra_release, depends_on=[ou_orchestra_release], custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="10m", - delete="10m" - ) - ) + create="8m", update="10m", delete="10m" + ), + ), ) # Sanitize name for proxy - proxy_name = sanitize_name('proxy') + proxy_name = sanitize_name("proxy") - orchestra_kube_oidc_proxy_chart_name = 'orchestra-kube-oidc-proxy' - orchestra_kube_oidc_proxy_chart_version = get_latest_helm_chart_version(chart_index_url, orchestra_kube_oidc_proxy_chart_name) + orchestra_kube_oidc_proxy_chart_name = "orchestra-kube-oidc-proxy" + orchestra_kube_oidc_proxy_chart_version = get_latest_helm_chart_version( + chart_index_url, orchestra_kube_oidc_proxy_chart_name + ) ou_kube_oidc_proxy_release = k8s.helm.v3.Release( proxy_name, @@ -426,20 +398,16 @@ def update_values(name): version=orchestra_kube_oidc_proxy_chart_version, skip_await=False, wait_for_jobs=True, - repository_opts= k8s.helm.v3.RepositoryOptsArgs( - repo=chart_url - ), + repository_opts=k8s.helm.v3.RepositoryOptsArgs(repo=chart_url), ), opts=pulumi.ResourceOptions( - provider = k8s_provider, + provider=k8s_provider, parent=ou_orchestra_login_portal_release, depends_on=[ou_orchestra_login_portal_release], custom_timeouts=pulumi.CustomTimeouts( - create="8m", - update="10m", - delete="10m" - ) - ) + create="8m", update="10m", delete="10m" + ), + ), ) return version, operator_release diff --git a/pulumi/modules/openunison/encoded_assets.py b/pulumi/modules/openunison/encoded_assets.py index 10e73df..b4fc74c 100644 --- a/pulumi/modules/openunison/encoded_assets.py +++ b/pulumi/modules/openunison/encoded_assets.py @@ -1,12 +1,14 @@ import base64 import os + def encode_file_to_base64(file_name): file_path = os.path.join(os.path.dirname(__file__), "assets", file_name) with open(file_path, "rb") as file: encoded_string = base64.b64encode(file.read()).decode("utf-8") return encoded_string + def return_encoded_assets(): # Define file names kubevirt_png = "kubevirt.png" @@ -25,7 +27,7 @@ def return_encoded_assets(): "kubevirt_icon": kubevirt_icon, "prometheus_icon": prometheus_icon, "alertmanager_icon": alertmanager_icon, - "grafana_icon": grafana_icon + "grafana_icon": grafana_icon, } return assets diff --git a/pulumi/modules/prometheus/deploy.py b/pulumi/modules/prometheus/deploy.py index a38a60c..7915965 100644 --- a/pulumi/modules/prometheus/deploy.py +++ b/pulumi/modules/prometheus/deploy.py @@ -15,11 +15,12 @@ from .types import PrometheusConfig + def deploy_prometheus_module( - config_prometheus: PrometheusConfig, - global_depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[str, Optional[pulumi.Resource]]: + config_prometheus: PrometheusConfig, + global_depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[str, Optional[pulumi.Resource]]: """ Deploys the Prometheus module and returns the version and the deployed resource. @@ -43,11 +44,12 @@ def deploy_prometheus_module( return prometheus_version, prometheus_resource + def deploy_prometheus( - config_prometheus: PrometheusConfig, - depends_on: List[pulumi.Resource], - k8s_provider: k8s.Provider, - ) -> Tuple[str, Optional[pulumi.Resource]]: + config_prometheus: PrometheusConfig, + depends_on: List[pulumi.Resource], + k8s_provider: k8s.Provider, +) -> Tuple[str, Optional[pulumi.Resource]]: """ Deploys Prometheus using Helm and sets up necessary services. """ @@ -84,18 +86,18 @@ def deploy_prometheus( "users": { "allow_sign_up": False, "auto_assign_org": True, - "auto_assign_org_role": "Admin" + "auto_assign_org_role": "Admin", }, "auth.proxy": { "enabled": True, "header_name": "X-WEBAUTH-USER", "auto_sign_up": True, - "headers": "Groups:X-WEBAUTH-GROUPS" - } + "headers": "Groups:X-WEBAUTH-GROUPS", + }, } } } - else : + else: prometheus_helm_values = {} # Create the Helm Release @@ -127,13 +129,14 @@ def deploy_prometheus( return version, release + def create_prometheus_services( - config_prometheus: PrometheusConfig, - k8s_provider: k8s.Provider, - namespace: str, - parent: pulumi.Resource, - depends_on: List[pulumi.Resource], - ) -> List[k8s.core.v1.Service]: + config_prometheus: PrometheusConfig, + k8s_provider: k8s.Provider, + namespace: str, + parent: pulumi.Resource, + depends_on: List[pulumi.Resource], +) -> List[k8s.core.v1.Service]: """ Creates Prometheus, Grafana, and Alertmanager services. @@ -167,7 +170,7 @@ def create_prometheus_services( "port": 9090, "targetPort": 9090, "selector": "app.kubernetes.io/name", - } + }, ] # Create services from list of service definitions @@ -183,19 +186,21 @@ def create_prometheus_services( ), spec=k8s.core.v1.ServiceSpecArgs( type="ClusterIP", - ports=[k8s.core.v1.ServicePortArgs( - name="http-web", - port=service_def["port"], - protocol="TCP", - target_port=service_def["targetPort"], - )], + ports=[ + k8s.core.v1.ServicePortArgs( + name="http-web", + port=service_def["port"], + protocol="TCP", + target_port=service_def["targetPort"], + ) + ], selector={service_def["selector"]: service_def["name"]}, ), opts=pulumi.ResourceOptions( provider=k8s_provider, parent=parent, depends_on=depends_on, - ) + ), ) services.append(service) diff --git a/pulumi/modules/prometheus/types.py b/pulumi/modules/prometheus/types.py index 72a3956..e162fd2 100644 --- a/pulumi/modules/prometheus/types.py +++ b/pulumi/modules/prometheus/types.py @@ -7,6 +7,7 @@ from dataclasses import dataclass, field from typing import Optional, Dict, Any + @dataclass class PrometheusConfig: version: Optional[str] = None @@ -16,11 +17,13 @@ class PrometheusConfig: annotations: Dict[str, Any] = field(default_factory=dict) @staticmethod - def merge(user_config: Dict[str, Any]) -> 'PrometheusConfig': + def merge(user_config: Dict[str, Any]) -> "PrometheusConfig": default_config = PrometheusConfig() for key, value in user_config.items(): if hasattr(default_config, key): setattr(default_config, key, value) else: - pulumi.log.warn(f"Unknown configuration key '{key}' in prometheus config.") + pulumi.log.warn( + f"Unknown configuration key '{key}' in prometheus config." + ) return default_config diff --git a/pulumi/modules/vm/talos.py b/pulumi/modules/vm/talos.py index cd4ffb5..cbc4631 100644 --- a/pulumi/modules/vm/talos.py +++ b/pulumi/modules/vm/talos.py @@ -1,18 +1,21 @@ import pulumi import pulumi_kubernetes as k8s + def deploy_talos_cluster( - config_talos: dict, - k8s_provider: k8s.Provider, - parent, - depends_on: pulumi.Output[list], - ): + config_talos: dict, + k8s_provider: k8s.Provider, + parent, + depends_on: pulumi.Output[list], +): """ Deploy the Talos controlplane and worker VirtualMachinePools based on the provided configuration. """ # Get configurations for controlplane and workers, with defaults applied - controlplane_config = get_talos_config(config_talos.get("controlplane", {}), "controlplane") + controlplane_config = get_talos_config( + config_talos.get("controlplane", {}), "controlplane" + ) worker_config = get_talos_config(config_talos.get("workers", {}), "workers") # Apply the running flag to both configurations @@ -24,7 +27,7 @@ def deploy_talos_cluster( config_vm=controlplane_config, k8s_provider=k8s_provider, depends_on=depends_on, - parent=parent + parent=parent, ) # Deploy the Talos workers (if replicas > 0) @@ -34,24 +37,24 @@ def deploy_talos_cluster( config_vm=worker_config, k8s_provider=k8s_provider, depends_on=depends_on, - parent=parent + parent=parent, ) return controlplane_vm_pool, worker_vm_pool -def get_talos_config( - config_talos_cluster: dict, - node_type: str - ) -> dict: + +def get_talos_config(config_talos_cluster: dict, node_type: str) -> dict: """ Generate the Talos cluster configuration by merging common default values with node-specific config. """ # Common default configuration for both controlplane and workers common_talos_defaults = { "namespace": "default", - "image": config_talos_cluster.get("image", "docker.io/containercraft/talos:1.7.6"), + "image": config_talos_cluster.get( + "image", "docker.io/containercraft/talos:1.7.6" + ), "network_name": "br0", # Default network - "running": True # Default running state + "running": True, # Default running state } # Set vm_pool_name for controlplane and workers @@ -60,24 +63,32 @@ def get_talos_config( # Handle controlplane configuration if node_type == "controlplane": controlplane_replicas = 1 # Default to single - controlplane_config = config_talos_cluster.get('replicas', 'single') + controlplane_config = config_talos_cluster.get("replicas", "single") - if controlplane_config == 'single': + if controlplane_config == "single": controlplane_replicas = 1 - elif controlplane_config == 'ha': + elif controlplane_config == "ha": controlplane_replicas = 3 else: - pulumi.log.error(f"Unrecognized controlplane replica config. Expected 'single' or 'ha', got: {controlplane_config}") + pulumi.log.error( + f"Unrecognized controlplane replica config. Expected 'single' or 'ha', got: {controlplane_config}" + ) raise ValueError(f"Invalid controlplane config: {controlplane_config}") # Controlplane-specific defaults and configuration overrides controlplane_defaults = { "replicas": controlplane_replicas, "cpu_cores": config_talos_cluster.get("cpu_cores", 1), - "memory_size": config_talos_cluster.get("memory_size", "2"), # Memory in GiB - "root_disk_size": config_talos_cluster.get("root_disk_size", "32"), # Root disk size in GiB - "empty_disk_size": config_talos_cluster.get("empty_disk_size", "0"), # Empty disk size in GiB - "vm_pool_name": vm_pool_name + "memory_size": config_talos_cluster.get( + "memory_size", "2" + ), # Memory in GiB + "root_disk_size": config_talos_cluster.get( + "root_disk_size", "32" + ), # Root disk size in GiB + "empty_disk_size": config_talos_cluster.get( + "empty_disk_size", "0" + ), # Empty disk size in GiB + "vm_pool_name": vm_pool_name, } return {**common_talos_defaults, **controlplane_defaults} @@ -86,22 +97,26 @@ def get_talos_config( worker_defaults = { "replicas": config_talos_cluster.get("replicas", 0), # Worker replicas "cpu_cores": config_talos_cluster.get("cpu_cores", 2), # Worker CPU cores - "memory_size": config_talos_cluster.get("memory_size", "2"), # Worker memory in GiB - "root_disk_size": config_talos_cluster.get("root_disk_size", "32"), # Root disk size in GiB - "empty_disk_size": config_talos_cluster.get("empty_disk_size", "16"), # Empty disk size in GiB - "vm_pool_name": vm_pool_name + "memory_size": config_talos_cluster.get( + "memory_size", "2" + ), # Worker memory in GiB + "root_disk_size": config_talos_cluster.get( + "root_disk_size", "32" + ), # Root disk size in GiB + "empty_disk_size": config_talos_cluster.get( + "empty_disk_size", "16" + ), # Empty disk size in GiB + "vm_pool_name": vm_pool_name, } return {**common_talos_defaults, **worker_defaults} else: raise ValueError(f"Unsupported node type: {node_type}") + def deploy_talos_cluster_controlplane( - config_vm, - k8s_provider: k8s.Provider, - depends_on: pulumi.Output[list], - parent - ): + config_vm, k8s_provider: k8s.Provider, depends_on: pulumi.Output[list], parent +): """ Deploy the Talos cluster controlplane with specific configuration. """ @@ -115,7 +130,7 @@ def deploy_talos_cluster_controlplane( empty_disk_size=config_vm["empty_disk_size"], image_address=config_vm["image"], network_name=config_vm["network_name"], - running=config_vm["running"] + running=config_vm["running"], ) controlplane_vm_pool = k8s.apiextensions.CustomResource( @@ -129,19 +144,17 @@ def deploy_talos_cluster_controlplane( spec=vm_pool_spec, opts=pulumi.ResourceOptions( provider=k8s_provider, - #depends_on=depends_on, - parent=parent - ) + # depends_on=depends_on, + parent=parent, + ), ) return controlplane_vm_pool + def deploy_talos_cluster_workers( - config_vm, - k8s_provider: k8s.Provider, - depends_on, - parent - ): + config_vm, k8s_provider: k8s.Provider, depends_on, parent +): """ Deploy the Talos workers with their specific configuration. """ @@ -156,7 +169,7 @@ def deploy_talos_cluster_workers( empty_disk_size=config_vm["empty_disk_size"], image_address=config_vm["image"], network_name=config_vm["network_name"], - running=config_vm["running"] + running=config_vm["running"], ) worker_vm_pool = k8s.apiextensions.CustomResource( @@ -170,27 +183,28 @@ def deploy_talos_cluster_workers( spec=worker_vm_pool_spec, opts=pulumi.ResourceOptions( provider=k8s_provider, - #depends_on=depends_on, - parent=parent - ) + # depends_on=depends_on, + parent=parent, + ), ) else: worker_vm_pool = None return worker_vm_pool + def generate_talos_vmpool_spec( - vm_pool_name: str, - namespace: str, - replicas: int, - cpu_cores: int, - memory_size: str, - root_disk_size: str, - empty_disk_size: str, - image_address: str, - network_name: str, - running: bool - ) -> dict: + vm_pool_name: str, + namespace: str, + replicas: int, + cpu_cores: int, + memory_size: str, + root_disk_size: str, + empty_disk_size: str, + image_address: str, + network_name: str, + running: bool, +) -> dict: """ Generate the VirtualMachinePool spec for Talos VMs. """ @@ -200,38 +214,19 @@ def generate_talos_vmpool_spec( # Initialize the spec with a root disk data volume template spec = { "replicas": replicas, - "selector": { - "matchLabels": { - "kubevirt.io/vmpool": vm_pool_name - } - }, + "selector": {"matchLabels": {"kubevirt.io/vmpool": vm_pool_name}}, "virtualMachineTemplate": { - "metadata": { - "labels": { - "kubevirt.io/vmpool": vm_pool_name - } - }, + "metadata": {"labels": {"kubevirt.io/vmpool": vm_pool_name}}, "spec": { "running": running, "template": { - "metadata": { - "labels": { - "kubevirt.io/vmpool": vm_pool_name - } - }, + "metadata": {"labels": {"kubevirt.io/vmpool": vm_pool_name}}, "spec": { "networks": [ - { - "name": "eth0", - "multus": { - "networkName": network_name - } - } + {"name": "eth0", "multus": {"networkName": network_name}} ], "domain": { - "cpu": { - "cores": cpu_cores # Use configured CPU cores - }, + "cpu": {"cores": cpu_cores}, # Use configured CPU cores "resources": { "requests": { "memory": f"{memory_size}Gi" # Use configured memory size @@ -242,34 +237,23 @@ def generate_talos_vmpool_spec( { "name": "talos-root-disk", "bootOrder": 1, - "disk": { - "bus": "virtio" - } + "disk": {"bus": "virtio"}, } ], - "interfaces": [ - { - "name": "eth0", - "bridge": {} - } - ] - } + "interfaces": [{"name": "eth0", "bridge": {}}], + }, }, "volumes": [ { "name": "talos-root-disk", - "dataVolume": { - "name": f"{vm_pool_name}-root-dv" - } + "dataVolume": {"name": f"{vm_pool_name}-root-dv"}, } - ] - } + ], + }, }, "dataVolumeTemplates": [ { - "metadata": { - "name": f"{vm_pool_name}-root-dv" - }, + "metadata": {"name": f"{vm_pool_name}-root-dv"}, "spec": { "storage": { "accessModes": ["ReadWriteOnce"], @@ -277,46 +261,37 @@ def generate_talos_vmpool_spec( "requests": { "storage": f"{root_disk_size}Gi" # Use configured root disk size } - } + }, }, "source": { "registry": { "url": docker_image_address, # Ensure the correct image URL is propagated here } - } - } + }, + }, } - ] - } - } + ], + }, + }, } # If the empty disk size is greater than 0, add the empty disk to the spec if int(empty_disk_size) > 0: - spec["virtualMachineTemplate"]["spec"]["template"]["spec"]["domain"]["devices"]["disks"].append( - { - "name": "talos-empty-disk", - "disk": { - "bus": "virtio" - } - } - ) + spec["virtualMachineTemplate"]["spec"]["template"]["spec"]["domain"]["devices"][ + "disks" + ].append({"name": "talos-empty-disk", "disk": {"bus": "virtio"}}) spec["virtualMachineTemplate"]["spec"]["template"]["spec"]["volumes"].append( { "name": "talos-empty-disk", - "dataVolume": { - "name": f"{vm_pool_name}-empty-dv" - } + "dataVolume": {"name": f"{vm_pool_name}-empty-dv"}, } ) # Append the empty disk data volume template spec["virtualMachineTemplate"]["spec"]["dataVolumeTemplates"].append( { - "metadata": { - "name": f"{vm_pool_name}-empty-dv" - }, + "metadata": {"name": f"{vm_pool_name}-empty-dv"}, "spec": { "storage": { "accessModes": ["ReadWriteOnce"], @@ -324,12 +299,10 @@ def generate_talos_vmpool_spec( "requests": { "storage": f"{empty_disk_size}Gi" # Use configured empty disk size } - } + }, }, - "source": { - "blank": {} - } - } + "source": {"blank": {}}, + }, } ) diff --git a/pulumi/modules/vm/ubuntu.py b/pulumi/modules/vm/ubuntu.py index 13db98e..b7cb7d4 100644 --- a/pulumi/modules/vm/ubuntu.py +++ b/pulumi/modules/vm/ubuntu.py @@ -2,11 +2,8 @@ import pulumi import pulumi_kubernetes as k8s -def deploy_ubuntu_vm( - config_vm, - k8s_provider: k8s.Provider, - depends_on: list = [] - ): + +def deploy_ubuntu_vm(config_vm, k8s_provider: k8s.Provider, depends_on: list = []): # Extract configuration values from config_vm namespace = config_vm.get("namespace", "default") instance_name = config_vm.get("instance_name", "ubuntu") @@ -28,7 +25,7 @@ def deploy_ubuntu_vm( string_data={ "key1": ssh_pub_key, }, - opts=pulumi.ResourceOptions(provider=k8s_provider, depends_on=depends_on) + opts=pulumi.ResourceOptions(provider=k8s_provider, depends_on=depends_on), ) # Define the Service @@ -40,17 +37,19 @@ def deploy_ubuntu_vm( ), spec=k8s.core.v1.ServiceSpecArgs( type="NodePort", - ports=[k8s.core.v1.ServicePortArgs( - node_port=node_port, - port=node_port, - protocol="TCP", - target_port=22, - )], + ports=[ + k8s.core.v1.ServicePortArgs( + node_port=node_port, + port=node_port, + protocol="TCP", + target_port=22, + ) + ], selector={ f"{app_name}.ccio.io/instance": instance_name, }, ), - opts=pulumi.ResourceOptions(provider=k8s_provider, depends_on=depends_on) + opts=pulumi.ResourceOptions(provider=k8s_provider, depends_on=depends_on), ) # Cloud-init user data for VM configuration @@ -116,15 +115,10 @@ def deploy_ubuntu_vm( "cpu": { "model": "host-passthrough", "dedicatedCpuPlacement": False, - "isolateEmulatorThread": False + "isolateEmulatorThread": False, }, "memory": {"guest": "4096M"}, - "resources": { - "limits": { - "memory": "4Gi", - "cpu": 2 - } - }, + "resources": {"limits": {"memory": "4Gi", "cpu": 2}}, "devices": { "rng": {}, "autoattachPodInterface": False, @@ -132,39 +126,55 @@ def deploy_ubuntu_vm( "autoattachGraphicsDevice": True, "networkInterfaceMultiqueue": False, "disks": [ - {"name": "containerdisk", "bootOrder": 1, "disk": {"bus": "virtio"}}, - {"name": "cloudinitdisk", "disk": {"bus": "virtio"}} + { + "name": "containerdisk", + "bootOrder": 1, + "disk": {"bus": "virtio"}, + }, + {"name": "cloudinitdisk", "disk": {"bus": "virtio"}}, ], "interfaces": [ {"name": "enp1s0", "model": "virtio", "bridge": {}} ], }, - "machine": {"type": "q35"} + "machine": {"type": "q35"}, }, "networks": [{"name": "enp1s0", "pod": {}}], "terminationGracePeriodSeconds": 0, - "accessCredentials": [{ - "sshPublicKey": { - "source": { - "secret": {"secretName": kc2_pubkey_secret.metadata["name"]} - }, - "propagationMethod": { - "qemuGuestAgent": {"users": [ssh_user]} + "accessCredentials": [ + { + "sshPublicKey": { + "source": { + "secret": { + "secretName": kc2_pubkey_secret.metadata["name"] + } + }, + "propagationMethod": { + "qemuGuestAgent": {"users": [ssh_user]} + }, } } - }], + ], "volumes": [ - {"name": "containerdisk", - "containerDisk": {"image": image_name, "imagePullPolicy": "Always"}}, - {"name": "cloudinitdisk", "cloudInitNoCloud": { - "networkData": network_data, - "userData": user_data, - }} - ] - } - } + { + "name": "containerdisk", + "containerDisk": { + "image": image_name, + "imagePullPolicy": "Always", + }, + }, + { + "name": "cloudinitdisk", + "cloudInitNoCloud": { + "networkData": network_data, + "userData": user_data, + }, + }, + ], + }, + }, }, - opts=pulumi.ResourceOptions(provider=k8s_provider, depends_on=depends_on) + opts=pulumi.ResourceOptions(provider=k8s_provider, depends_on=depends_on), ) # Export the Service URL and VM name as outputs diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5ad61b6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[tool.poetry] +name = "konductor" +version = "0.1.0" +description = "DevOps Platform IaC Template Repository" +authors = ["Your Name "] +readme = "README.md" +packages = [{include = "pulumi"}] + +[tool.poetry.dependencies] +python = "^3.8" +pulumi = "^3.0.0" +pulumi-aws = "^6.0.0" +pulumi-kubernetes = "^4.0.0" +pulumi-aws-native = "^0.90.0" +pydantic = "^2.0.0" +gitpython = "^3.1.0" +semver = "^3.0.0" +packaging = "^23.0" +pyright = "^1.1.387" + +[tool.poetry.group.dev.dependencies] +pyright = "^1.1.0" +black = "^23.0.0" +pylint = "^3.0.0" +pytest = "^7.0.0" +pytest-cov = "^4.0.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.pyright] +include = ["pulumi"] +exclude = ["**/node_modules", "**/__pycache__"] +typeCheckingMode = "basic" +useLibraryCodeForTypes = true +reportMissingImports = "warning" +reportMissingTypeStubs = false + +[tool.black] +line-length = 88 +target-version = ['py38'] +include = '\.pyi?$' + +[tool.pylint.messages_control] +disable = [ + "C0103", # invalid-name + "C0330", # bad-continuation + "R0903", # too-few-public-methods +] diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..842c9d2 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,40 @@ +{ + "include": [ + "pulumi" + ], + "exclude": [ + "**/node_modules", + "**/__pycache__", + "**/.pytest_cache", + ".venv", + "venv" + ], + "ignore": [], + "defineConstant": { + "DEBUG": true + }, + "stubPath": "typings", + "pythonVersion": "3.10", + "pythonPlatform": "All", + "typeCheckingMode": "basic", + "reportPrivateUsage": "warning", + "strictListInference": false, + "reportMissingImports": "warning", + "useLibraryCodeForTypes": true, + "reportMissingTypeStubs": false, + "strictParameterNoneValue": false, + "strictDictionaryInference": false, + "reportUntypedBaseClass": "warning", + "reportInvalidTypeVarUse": "warning", + "reportUnknownMemberType": "warning", + "reportUnknownLambdaType": "warning", + "reportMissingTypeArgument": "warning", + "reportUnknownArgumentType": "warning", + "reportUnknownVariableType": "warning", + "reportUnknownParameterType": "warning", + "reportMissingParameterType": "warning", + "reportUntypedClassDecorator": "warning", + "reportPropertyTypeMismatch": "warning", + "reportUntypedFunctionDecorator": "warning", + "reportInvalidStringEscapeSequence": "warning" +}