From 8d01a6b06b56da963496522e2d025c38b57f29dd Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 24 Jul 2023 16:58:12 +0300 Subject: [PATCH 01/95] add default integration folder structure --- integrations/azure/.dockerignore | 6 + integrations/azure/.gitignore | 153 ++ integrations/azure/.port/defaults/.gitignore | 1 + integrations/azure/.port/resources/.gitignore | 1 + integrations/azure/.port/spec.yaml | 15 + integrations/azure/CHANGELOG.md | 8 + integrations/azure/Dockerfile | 16 + integrations/azure/Makefile | 71 + integrations/azure/README.md | 47 + integrations/azure/changelog/.gitignore | 1 + integrations/azure/config.yaml | 17 + integrations/azure/debug.py | 4 + integrations/azure/main.py | 41 + integrations/azure/poetry.lock | 2009 +++++++++++++++++ integrations/azure/poetry.toml | 3 + integrations/azure/pyproject.toml | 105 + integrations/azure/tests/__init__.py | 0 17 files changed, 2498 insertions(+) create mode 100644 integrations/azure/.dockerignore create mode 100644 integrations/azure/.gitignore create mode 100644 integrations/azure/.port/defaults/.gitignore create mode 100644 integrations/azure/.port/resources/.gitignore create mode 100644 integrations/azure/.port/spec.yaml create mode 100644 integrations/azure/CHANGELOG.md create mode 100644 integrations/azure/Dockerfile create mode 100644 integrations/azure/Makefile create mode 100644 integrations/azure/README.md create mode 100644 integrations/azure/changelog/.gitignore create mode 100644 integrations/azure/config.yaml create mode 100644 integrations/azure/debug.py create mode 100644 integrations/azure/main.py create mode 100644 integrations/azure/poetry.lock create mode 100644 integrations/azure/poetry.toml create mode 100644 integrations/azure/pyproject.toml create mode 100644 integrations/azure/tests/__init__.py diff --git a/integrations/azure/.dockerignore b/integrations/azure/.dockerignore new file mode 100644 index 0000000000..2ceaf5c08d --- /dev/null +++ b/integrations/azure/.dockerignore @@ -0,0 +1,6 @@ +.vscode/ +assets/ +*.md +.git +.gitignore +.env \ No newline at end of file diff --git a/integrations/azure/.gitignore b/integrations/azure/.gitignore new file mode 100644 index 0000000000..d550eaaeaf --- /dev/null +++ b/integrations/azure/.gitignore @@ -0,0 +1,153 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ \ No newline at end of file diff --git a/integrations/azure/.port/defaults/.gitignore b/integrations/azure/.port/defaults/.gitignore new file mode 100644 index 0000000000..f935021a8f --- /dev/null +++ b/integrations/azure/.port/defaults/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/integrations/azure/.port/resources/.gitignore b/integrations/azure/.port/resources/.gitignore new file mode 100644 index 0000000000..f935021a8f --- /dev/null +++ b/integrations/azure/.port/resources/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml new file mode 100644 index 0000000000..a2aa2b2d7d --- /dev/null +++ b/integrations/azure/.port/spec.yaml @@ -0,0 +1,15 @@ +version: v0.1.0 +type: gitlab +description: azure integration for Port Ocean +icon: Cookiecutter +features: + - type: exporter + section: Git Providers + resources: + - kind: + - kind: +configurations: + - name: subscriptionId + required: true + type: string + sensitive: true \ No newline at end of file diff --git a/integrations/azure/CHANGELOG.md b/integrations/azure/CHANGELOG.md new file mode 100644 index 0000000000..14e279d66d --- /dev/null +++ b/integrations/azure/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + diff --git a/integrations/azure/Dockerfile b/integrations/azure/Dockerfile new file mode 100644 index 0000000000..cdb8b20ce9 --- /dev/null +++ b/integrations/azure/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.11-slim-buster + +ENV LIBRDKAFKA_VERSION 1.9.2 + +WORKDIR /app + +RUN apt update && apt install -y wget make g++ libssl-dev autoconf automake libtool curl +RUN wget https://github.com/edenhill/librdkafka/archive/v${LIBRDKAFKA_VERSION}.tar.gz && \ + tar xvzf v${LIBRDKAFKA_VERSION}.tar.gz && \ + (cd librdkafka-${LIBRDKAFKA_VERSION}/ && ./configure && make && make install && ldconfig) + +COPY . /app + +RUN make install + +ENTRYPOINT make run \ No newline at end of file diff --git a/integrations/azure/Makefile b/integrations/azure/Makefile new file mode 100644 index 0000000000..399de69631 --- /dev/null +++ b/integrations/azure/Makefile @@ -0,0 +1,71 @@ +ACTIVATE := . .venv/bin/activate + +define run_checks + exit_code=0; \ + cd $1; \ + poetry check || exit_code=$$?;\ + poetry lock --check || exit_code=$$?;\ + mypy . || exit_code=$$?; \ + ruff . || exit_code=$$?; \ + black --check . || exit_code=$$?; \ + if [ $$exit_code -eq 1 ]; then \ + echo "\033[0;31mOne or more checks failed with exit code $$exit_code\033[0m"; \ + else \ + echo "\033[0;32mAll checks executed successfully.\033[0m"; \ + fi; \ + exit $$exit_code +endef + +define install_poetry + if ! command -v poetry &> /dev/null; then \ + pip install --upgrade pip; \ + pip install poetry; \ + else \ + echo "Poetry is already installed."; \ + fi +endef + +define deactivate_virtualenv + if [ -n "$$VIRTUAL_ENV" ]; then \ + unset VIRTUAL_ENV; \ + unset PYTHONHOME; \ + unset -f pydoc >/dev/null 2>&1; \ + OLD_PATH="$$PATH"; \ + PATH=$$(echo -n "$$PATH" | awk -v RS=: -v ORS=: '/\/virtualenv\/bin$$/ {next} {print}'); \ + export PATH; \ + hash -r; \ + echo "Deactivated the virtual environment."; \ + fi +endef + +.SILENT: install lint run test clean + +install: + $(call deactivate_virtualenv) && \ + $(call install_poetry) && \ + poetry install --with dev + +lint: + $(ACTIVATE) && \ + $(call run_checks,.) + +run: + $(ACTIVATE) && ocean sail + +test: lint + $(ACTIVATE) && poetry run pytest + +clean: + @find . -name '.venv' -type d -exec rm -rf {} \; + @find . -name '*.pyc' -exec rm -rf {} \; + @find . -name '__pycache__' -exec rm -rf {} \; + @find . -name 'Thumbs.db' -exec rm -rf {} \; + @find . -name '*~' -exec rm -rf {} \; + rm -rf .cache + rm -rf build + rm -rf dist + rm -rf *.egg-info + rm -rf htmlcov + rm -rf .tox/ + rm -rf docs/_build + rm -rf dist/ \ No newline at end of file diff --git a/integrations/azure/README.md b/integrations/azure/README.md new file mode 100644 index 0000000000..9ad4e2c263 --- /dev/null +++ b/integrations/azure/README.md @@ -0,0 +1,47 @@ +# Azure + +Azure integration + +## Development Requirements + +- Python3.11.0 +- Poetry (Python Package Manager) +- Port-Ocean + +## Installation + +```sh +make install +``` + +## Runnning Localhost +```sh +make run +``` +or +```sh +ocean sail +``` + +## Running Tests + +`make test` + +## Access Swagger Documentation + +> + +## Access Redoc Documentation + +> + + +## Folder Structure +The azure integration suggested folder structure is as follows: + +``` +azure/ +├─ main.py +├─ pyproject.toml +└─ Dockerfile +``` \ No newline at end of file diff --git a/integrations/azure/changelog/.gitignore b/integrations/azure/changelog/.gitignore new file mode 100644 index 0000000000..f935021a8f --- /dev/null +++ b/integrations/azure/changelog/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml new file mode 100644 index 0000000000..c32eb06cd2 --- /dev/null +++ b/integrations/azure/config.yaml @@ -0,0 +1,17 @@ +# This is an example configuration file for the integration service. +# Please copy this file to config.yaml file in the integration folder and edit it to your needs. + +port: + clientId: {{ from env PORT_CLIENT_ID }} # Can be loaded via environment variable: PORT_CLIENT_ID + clientSecret: {{ from env PORT_CLIENT_SECRET }} # Can be loaded via environment variable: PORT_CLIENT_SECRET +# The event listener to use for the integration service. +eventListener: + type: KAFKA +integration: + # The identifier of this integration instance. + identifier: {{ from env INTEGRATION_IDENTIFIER }} + # The type of the integration. + type: "My Integration type (Gitlab, Jira, etc.)" + config: + myGitToken: {{ from env MY_GIT_TOKEN }} + someApplicationUrl: "https://I-Am-Not-A-Real-Url.com" \ No newline at end of file diff --git a/integrations/azure/debug.py b/integrations/azure/debug.py new file mode 100644 index 0000000000..40b79f2d1c --- /dev/null +++ b/integrations/azure/debug.py @@ -0,0 +1,4 @@ +from port_ocean import run + +if __name__ == "__main__": + run() diff --git a/integrations/azure/main.py b/integrations/azure/main.py new file mode 100644 index 0000000000..48f98141e9 --- /dev/null +++ b/integrations/azure/main.py @@ -0,0 +1,41 @@ +from typing import Any + +from port_ocean.context.ocean import ocean + + +# Required +# Listen to the resync event of all the kinds specified in the mapping inside port. +# Called each time with a different kind that should be returned from the source system. +@ocean.on_resync() +async def on_resync(kind: str) -> list[dict[Any, Any]]: + # 1. Get all data from the source system + # 2. Return a list of dictionaries with the raw data of the state to run the core logic of the framework for + # Example: + # if kind == "project": + # return [{"some_project_key": "someProjectValue", ...}] + # if kind == "issues": + # return [{"some_issue_key": "someIssueValue", ...}] + return [] + + +# The same sync logic can be registered for one of the kinds that are available in the mapping in port. +# @ocean.on_resync('project') +# async def resync_project(kind: str) -> list[dict[Any, Any]]: +# # 1. Get all projects from the source system +# # 2. Return a list of dictionaries with the raw data of the state +# return [{"some_project_key": "someProjectValue", ...}] +# +# @ocean.on_resync('issues') +# async def resync_issues(kind: str) -> list[dict[Any, Any]]: +# # 1. Get all issues from the source system +# # 2. Return a list of dictionaries with the raw data of the state +# return [{"some_issue_key": "someIssueValue", ...}] + + +# Optional +# Listen to the start event of the integration. Called once when the integration starts. +@ocean.on_start() +async def on_start() -> None: + # Something to do when the integration starts + # For example create a client to query 3rd party services - GitHub, Jira, etc... + print("Starting integration") diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock new file mode 100644 index 0000000000..65884909c6 --- /dev/null +++ b/integrations/azure/poetry.lock @@ -0,0 +1,2009 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.8.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "arrow" +version = "1.2.3" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, + {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" + +[[package]] +name = "astroid" +version = "2.15.6" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, + {file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "azure-common" +version = "1.1.28" +description = "Microsoft Azure Client Library for Python (Common)" +optional = false +python-versions = "*" +files = [ + {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"}, + {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"}, +] + +[[package]] +name = "azure-core" +version = "1.28.0" +description = "Microsoft Azure Core Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-core-1.28.0.zip", hash = "sha256:e9eefc66fc1fde56dab6f04d4e5d12c60754d5a9fa49bdcfd8534fc96ed936bd"}, + {file = "azure_core-1.28.0-py3-none-any.whl", hash = "sha256:dec36dfc8eb0b052a853f30c07437effec2f9e3e1fc8f703d9bdaa5cfc0043d9"}, +] + +[package.dependencies] +requests = ">=2.18.4" +six = ">=1.11.0" +typing-extensions = ">=4.3.0" + +[package.extras] +aio = ["aiohttp (>=3.0)"] + +[[package]] +name = "azure-identity" +version = "1.13.0" +description = "Microsoft Azure Identity Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-identity-1.13.0.zip", hash = "sha256:c931c27301ffa86b07b4dcf574e29da73e3deba9ab5d1fe4f445bb6a3117e260"}, + {file = "azure_identity-1.13.0-py3-none-any.whl", hash = "sha256:bd700cebb80cd9862098587c29d8677e819beca33c62568ced6d5a8e5e332b82"}, +] + +[package.dependencies] +azure-core = ">=1.11.0,<2.0.0" +cryptography = ">=2.5" +msal = ">=1.20.0,<2.0.0" +msal-extensions = ">=0.3.0,<2.0.0" +six = ">=1.12.0" + +[[package]] +name = "azure-mgmt-core" +version = "1.4.0" +description = "Microsoft Azure Management Core Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-mgmt-core-1.4.0.zip", hash = "sha256:d195208340094f98e5a6661b781cde6f6a051e79ce317caabd8ff97030a9b3ae"}, + {file = "azure_mgmt_core-1.4.0-py3-none-any.whl", hash = "sha256:81071675f186a585555ef01816f2774d49c1c9024cb76e5720c3c0f6b337bb7d"}, +] + +[package.dependencies] +azure-core = ">=1.26.2,<2.0.0" + +[[package]] +name = "azure-mgmt-resource" +version = "23.0.1" +description = "Microsoft Azure Resource Management Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "azure-mgmt-resource-23.0.1.zip", hash = "sha256:c2ba6cfd99df95f55f36eadc4245e3dc713257302a1fd0277756d94bd8cb28e0"}, + {file = "azure_mgmt_resource-23.0.1-py3-none-any.whl", hash = "sha256:f185eec72bbc39f42bcb83ae6f1bad744f0e3f20a12d9b2b3e70d16c74ad9cc0"}, +] + +[package.dependencies] +azure-common = ">=1.1,<2.0" +azure-mgmt-core = ">=1.3.2,<2.0.0" +isodate = ">=0.6.1,<1.0.0" + +[[package]] +name = "binaryornot" +version = "0.4.4" +description = "Ultra-lightweight pure Python package to check if a file is binary or text." +optional = false +python-versions = "*" +files = [ + {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, + {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, +] + +[package.dependencies] +chardet = ">=3.0.2" + +[[package]] +name = "black" +version = "23.7.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "chardet" +version = "5.1.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, + {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.2.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.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.6" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "click-default-group" +version = "1.2.2" +description = "Extends click.Group to invoke a command without explicit subcommand name" +optional = false +python-versions = "*" +files = [ + {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, +] + +[package.dependencies] +click = "*" + +[[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 = "confluent-kafka" +version = "2.2.0" +description = "Confluent's Python client for Apache Kafka" +optional = false +python-versions = "*" +files = [ + {file = "confluent-kafka-2.2.0.tar.gz", hash = "sha256:c91a79cd0420eec98d547bdc974232619831d8ca5214eade55443cdf1015b0bc"}, + {file = "confluent_kafka-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:069830518594f2639025b60351bc44a9ef9e33a3cf648706854e6f84dbbf2fa4"}, + {file = "confluent_kafka-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6af70f3a8a4ad3df232c8d25658f87fe6318daa85867851a469cd853859cc976"}, + {file = "confluent_kafka-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c544064acd50c8f4cb5b6e2158f0dd046fe36ce49a3bf69f018d67d8aa31252a"}, + {file = "confluent_kafka-2.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0ecfb19465d2272e985b2cf8d02cbc6b1e878ad24fe342e7489a22bb0694e0ff"}, + {file = "confluent_kafka-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bfe649c950db27b36fe099e7dec3e6f9f6264dcd683fefca1a3d1baafea71489"}, + {file = "confluent_kafka-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:994fcf16cb59906558921fa58c9357c8211feef534a0218a75acd805786c67bd"}, + {file = "confluent_kafka-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36d74ae7880c504e347dca6f573681c6e17d35e000b187300dfdde1250330636"}, + {file = "confluent_kafka-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4df1118f5ed39a59aed3c4e1b94ec7d369f30a99e7609cb1eebc789cd2c0f86c"}, + {file = "confluent_kafka-2.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3f8033fabdd32e51b8fec44783b5d54ce7e2569207b42536b822c44911a06b4e"}, + {file = "confluent_kafka-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f6ff4b71ea8b5b58fc38a401481dc0406b501a9f469e0a3c7920db379b4667c8"}, + {file = "confluent_kafka-2.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:aa78d70c12e04c3331ac34b00e59f3216aacb140e464f3d55d965741e9cbae7a"}, + {file = "confluent_kafka-2.2.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93cd00b95f317c190e1c73266ff63c2001c08db141f3543792970f8019311d12"}, + {file = "confluent_kafka-2.2.0-cp36-cp36m-manylinux_2_28_aarch64.whl", hash = "sha256:76ae8f773e7b34db4d1ca7e83ca3b7bca4d75172aee71cb49db0adf30e091156"}, + {file = "confluent_kafka-2.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:59596a6d6e53cebb5dc020edf972700cb93f147703638bca9ff659e5ba41b096"}, + {file = "confluent_kafka-2.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a80e7202b361466fe275b76e8b76e7a89e2933099e4a1fbb5a5b71ccc3898647"}, + {file = "confluent_kafka-2.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0099be6c21c54c09b18f99d40b9189b20d9779af520b79320dc3be1b954a6735"}, + {file = "confluent_kafka-2.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:7fbc55cf74babe350974836691225920e2cc0acf51a49aac9896df4ca1ba6375"}, + {file = "confluent_kafka-2.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ee8f1ddb977d94be7daa30527eaded6357d62dbbe7fdff764df260f134f6e304"}, + {file = "confluent_kafka-2.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cabea69e2e6803eabaa99c8b72eb3747150bd9672bb819e16883750b37deae26"}, + {file = "confluent_kafka-2.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bdf449bd515d7ae859449e170ccf7f65d1ad9093ed5c8d7e4b6132a2388d1ae8"}, + {file = "confluent_kafka-2.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f5ea8b5669165a808a19f9aacae54e9606a4ccad40af1bb5da5c2ac95ad32c"}, + {file = "confluent_kafka-2.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:404b7c628721feaabc7c92e7a7308f2002b42d307aef2c22a62fc1554a8edea1"}, + {file = "confluent_kafka-2.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ab27f2c13e581b02ac34ce972f9a2f4165358c346f7ff8514d63ff13f422319d"}, + {file = "confluent_kafka-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9ded465e3dbdf3cae2be02aeed084b198832cbb97f08a433e83bb38fd6be9bf3"}, + {file = "confluent_kafka-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e24a41d7be375e96b0149646b676c923cab7871fbf782c485c740f7cc2d8a68"}, + {file = "confluent_kafka-2.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71cd9dbb7553f2fe3c5cac6fe47808e450aa4b09d9cbf448454fe049d8f603b2"}, + {file = "confluent_kafka-2.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:7b84e9b75b47646ca195d54bacca4b8cadc31ee676594cd7f4e5260ee09586be"}, + {file = "confluent_kafka-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:29bc97ec1bc42c8995a0fba50d055a6b968d9ad4f4e95277754df43fcc8195a8"}, +] + +[package.extras] +avro = ["avro (>=1.11.1,<2)", "fastavro (>=0.23.0,<1.0)", "fastavro (>=1.0)", "requests"] +dev = ["avro (>=1.11.1,<2)", "fastavro (>=0.23.0,<1.0)", "fastavro (>=1.0)", "flake8", "pytest", "pytest (==4.6.4)", "pytest-timeout", "requests"] +doc = ["avro (>=1.11.1,<2)", "fastavro (>=0.23.0,<1.0)", "fastavro (>=1.0)", "requests", "sphinx", "sphinx-rtd-theme"] +json = ["jsonschema", "pyrsistent", "pyrsistent (==0.16.1)", "requests"] +protobuf = ["protobuf", "requests"] +schema-registry = ["requests"] + +[[package]] +name = "cookiecutter" +version = "2.2.3" +description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cookiecutter-2.2.3-py3-none-any.whl", hash = "sha256:17ad6751aef0a39d004c5ecacbd18176de6e83505343073fd7e48b60bdac5254"}, + {file = "cookiecutter-2.2.3.tar.gz", hash = "sha256:d56f18c0c01c09804450b501ac43e8f6104cfa7cdd93610359c68b1ba9fd84d2"}, +] + +[package.dependencies] +arrow = "*" +binaryornot = ">=0.4.4" +click = ">=7.0,<9.0.0" +Jinja2 = ">=2.7,<4.0.0" +python-slugify = ">=4.0.0" +pyyaml = ">=5.3.1" +requests = ">=2.23.0" + +[[package]] +name = "cryptography" +version = "41.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, + {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, + {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, + {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "fastapi" +version = "0.100.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"}, + {file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "0.17.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "httpx" +version = "0.24.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, + {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, +] + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.18.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "incremental" +version = "22.10.0" +description = "\"A small library that versions your Python projects.\"" +optional = false +python-versions = "*" +files = [ + {file = "incremental-22.10.0-py2.py3-none-any.whl", hash = "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51"}, + {file = "incremental-22.10.0.tar.gz", hash = "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0"}, +] + +[package.extras] +mypy = ["click (>=6.0)", "mypy (==0.812)", "twisted (>=16.4.0)"] +scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] + +[[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 = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "loguru" +version = "0.7.0" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = ">=3.5" +files = [ + {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, + {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[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 = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "msal" +version = "1.23.0" +description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." +optional = false +python-versions = "*" +files = [ + {file = "msal-1.23.0-py2.py3-none-any.whl", hash = "sha256:3342e0837a047007f9d479e814b559c3219767453d57920dc40a31986862048b"}, + {file = "msal-1.23.0.tar.gz", hash = "sha256:25c9a33acf84301f93d1fdbe9f1a9c60cd38af0d5fffdbfa378138fc7bc1e86b"}, +] + +[package.dependencies] +cryptography = ">=0.6,<44" +PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} +requests = ">=2.0.0,<3" + +[package.extras] +broker = ["pymsalruntime (>=0.13.2,<0.14)"] + +[[package]] +name = "msal-extensions" +version = "1.0.0" +description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." +optional = false +python-versions = "*" +files = [ + {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"}, + {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"}, +] + +[package.dependencies] +msal = ">=0.4.1,<2.0.0" +portalocker = [ + {version = ">=1.0,<3", markers = "python_version >= \"3.5\" and platform_system != \"Windows\""}, + {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""}, +] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mypy" +version = "1.4.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[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 = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pathspec" +version = "0.11.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] + +[[package]] +name = "platformdirs" +version = "3.9.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "port-ocean" +version = "0.1.0" +description = "Port Ocean is a CLI tool for managing your Port projects." +optional = false +python-versions = ">=3.11,<4.0" +files = [ + {file = "port_ocean-0.1.0-py3-none-any.whl", hash = "sha256:cbbac3a1caa2700ab0fe05c0d2cb8575825f740152b992a44b8126e9a2f49720"}, + {file = "port_ocean-0.1.0.tar.gz", hash = "sha256:688d97988a452eac981855bc0e41029f758084e4f3061f7bd50808a172964e87"}, +] + +[package.dependencies] +click = {version = ">=8.1.3,<9.0.0", optional = true, markers = "extra == \"cli\""} +confluent-kafka = ">=2.1.1,<3.0.0" +cookiecutter = {version = ">=2.1.1,<3.0.0", optional = true, markers = "extra == \"cli\""} +fastapi = ">=0.100.0,<0.101.0" +httpx = ">=0.24.1,<0.25.0" +loguru = ">=0.7.0,<0.8.0" +pydantic = ">=1.10.8,<2.0.0" +pyjq = ">=2.6.0,<3.0.0" +pyyaml = ">=6.0,<7.0" +rich = {version = ">=13.4.1,<14.0.0", optional = true, markers = "extra == \"cli\""} +six = ">=1.16.0,<2.0.0" +urllib3 = ">=1.26.16,<2.0.0" +uvicorn = ">=0.22.0,<0.23.0" +werkzeug = ">=2.3.4,<3.0.0" + +[package.extras] +cli = ["click (>=8.1.3,<9.0.0)", "cookiecutter (>=2.1.1,<3.0.0)", "rich (>=13.4.1,<14.0.0)"] + +[[package]] +name = "portalocker" +version = "2.7.0" +description = "Wraps the portalocker recipe for easy usage" +optional = false +python-versions = ">=3.5" +files = [ + {file = "portalocker-2.7.0-py2.py3-none-any.whl", hash = "sha256:a07c5b4f3985c3cf4798369631fb7011adb498e2a46d8440efc75a8f29a0f983"}, + {file = "portalocker-2.7.0.tar.gz", hash = "sha256:032e81d534a88ec1736d03f780ba073f047a06c478b06e2937486f334e955c51"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)"] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "1.10.11" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, + {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, + {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, + {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, + {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, + {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, + {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, + {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, + {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, + {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyjq" +version = "2.6.0" +description = "Binding for jq JSON processor." +optional = false +python-versions = "*" +files = [ + {file = "pyjq-2.6.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:6e0e4f398e81b1fb9794874d81fc9240d4a155adba5a1aecda77e717bcfae03e"}, + {file = "pyjq-2.6.0.tar.gz", hash = "sha256:e083f326f4af8b07b8ca6424d1f99afbdd7db9b727284da5f919b9816077f2e4"}, +] + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pylint" +version = "2.17.4" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, + {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, +] + +[package.dependencies] +astroid = ">=2.15.4,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pytest" +version = "7.4.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-slugify" +version = "8.0.1" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"}, + {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[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 = "rich" +version = "13.4.2" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, + {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruff" +version = "0.0.278" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.278-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:1a90ebd8f2a554db1ee8d12b2f3aa575acbd310a02cd1a9295b3511a4874cf98"}, + {file = "ruff-0.0.278-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:38ca1c0c8c1221fe64c0a66784c91501d09a8ed02a4dbfdc117c0ce32a81eefc"}, + {file = "ruff-0.0.278-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c62a0bde4d20d087cabce2fa8b012d74c2e985da86d00fb3359880469b90e31"}, + {file = "ruff-0.0.278-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7545bb037823cd63dca19280f75a523a68bd3e78e003de74609320d6822b5a52"}, + {file = "ruff-0.0.278-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb380d2d6fdb60656a0b5fa78305535db513fc72ce11f4532cc1641204ef380"}, + {file = "ruff-0.0.278-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d11149c7b186f224f2055e437a030cd83b164a43cc0211314c33ad1553ed9c4c"}, + {file = "ruff-0.0.278-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666e739fb2685277b879d493848afe6933e3be30d40f41fe0e571ad479d57d77"}, + {file = "ruff-0.0.278-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec8b0469b54315803aaf1fbf9a37162a3849424cab6182496f972ad56e0ea702"}, + {file = "ruff-0.0.278-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c25b96602695a147d62a572865b753ef56aff1524abab13b9436724df30f9bd7"}, + {file = "ruff-0.0.278-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a48621f5f372d5019662db5b3dbfc5f1450f927683d75f1153fe0ebf20eb9698"}, + {file = "ruff-0.0.278-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1078125123a3c68e92463afacedb7e41b15ccafc09e510c6c755a23087afc8de"}, + {file = "ruff-0.0.278-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3ce0d620e257b4cad16e2f0c103b2f43a07981668a3763380542e8a131d11537"}, + {file = "ruff-0.0.278-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1cae4c07d334eb588f171f1363fa89a8911047eb93184276be11a24dbbc996c7"}, + {file = "ruff-0.0.278-py3-none-win32.whl", hash = "sha256:70d39f5599d8449082ab8ce542fa98e16413145eb411dd1dc16575b44565d52d"}, + {file = "ruff-0.0.278-py3-none-win_amd64.whl", hash = "sha256:e131595ab7f4ce61a1650463bd2fe304b49e7d0deb0dfa664b92817c97cdba5f"}, + {file = "ruff-0.0.278-py3-none-win_arm64.whl", hash = "sha256:737a0cfb6c36aaa92d97a46957dfd5e55329299074ad06ed12663b98e0c6fc82"}, + {file = "ruff-0.0.278.tar.gz", hash = "sha256:1a9f1d925204cfba81b18368b7ac943befcfccc3a41e170c91353b674c6b7a66"}, +] + +[[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 = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + +[[package]] +name = "tomlkit" +version = "0.11.8" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, +] + +[[package]] +name = "towncrier" +version = "23.6.0" +description = "Building newsfiles for your project." +optional = false +python-versions = ">=3.7" +files = [ + {file = "towncrier-23.6.0-py3-none-any.whl", hash = "sha256:da552f29192b3c2b04d630133f194c98e9f14f0558669d427708e203fea4d0a5"}, + {file = "towncrier-23.6.0.tar.gz", hash = "sha256:fc29bd5ab4727c8dacfbe636f7fb5dc53b99805b62da1c96b214836159ff70c1"}, +] + +[package.dependencies] +click = "*" +click-default-group = "*" +incremental = "*" +jinja2 = "*" + +[package.extras] +dev = ["furo", "packaging", "sphinx (>=5)", "twisted"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "urllib3" +version = "1.26.16" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.22.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, + {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "werkzeug" +version = "2.3.6" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, + {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +optional = false +python-versions = ">=3.5" +files = [ + {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, + {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, +] + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "0e99598fe2708ff9825b384b52b042838748c113f6b2b0175095e25928225bce" diff --git a/integrations/azure/poetry.toml b/integrations/azure/poetry.toml new file mode 100644 index 0000000000..53b35d370d --- /dev/null +++ b/integrations/azure/poetry.toml @@ -0,0 +1,3 @@ +[virtualenvs] +create = true +in-project = true diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml new file mode 100644 index 0000000000..1bfa825ab9 --- /dev/null +++ b/integrations/azure/pyproject.toml @@ -0,0 +1,105 @@ +[tool.poetry] +name = "azure" +version = "0.1.0" +description = "Azure integration" +authors = ["Tom Tankilevitch "] + +[tool.poetry.dependencies] +python = "^3.11" +port_ocean = { version = "^0.1.0", extras = ["cli"] } +azure-mgmt-resource = "^23.0.1" +azure-identity = "^1.13.0" +aiohttp = "^3.7.4" + +[tool.poetry.group.dev.dependencies] +pytest = "^7.2" +black = "^23.3.0" +mypy = "^1.3.0" +ruff = "^0.0.278" +pylint = "^2.17.4" +towncrier = "^23.6.0" + +[tool.towncrier] +directory = "changelog" +filename = "CHANGELOG.md" +package = "port_ocean" + + [[tool.towncrier.type]] + directory = "breaking" + name = "Breaking Changes" + showcontent = true + + [[tool.towncrier.type]] + directory = "deprecation" + name = "Deprecations" + showcontent = true + + [[tool.towncrier.type]] + directory = "feature" + name = "Features" + showcontent = true + + [[tool.towncrier.type]] + directory = "improvement" + name = "Improvements" + showcontent = true + + [[tool.towncrier.type]] + directory = "bugfix" + name = "Bug Fixes" + showcontent = true + + [[tool.towncrier.type]] + directory = "doc" + name = "Improved Documentation" + showcontent = true + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" + +[tool.mypy] +exclude = [ + 'venv', + '.venv', +] +plugins = [ + "pydantic.mypy" +] + +follow_imports = "silent" +warn_redundant_casts = true +warn_unused_ignores = true +disallow_any_generics = true +check_untyped_defs = true +no_implicit_reexport = true + +# for strict mypy: (this is the tricky one :-)) +disallow_untyped_defs = true + + +[tool.ruff] +# Never enforce `E501` (line length violations). +ignore = ["E501"] + +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true + +[tool.black] +line-length = 88 +target-version = ['py311'] +include = '\.pyi?$' +exclude = ''' +/( + \scripts + \.toml + |\.sh + |\.git + |\.ini + |Dockerfile + |\.venv +)/ +''' diff --git a/integrations/azure/tests/__init__.py b/integrations/azure/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From a2df0fe3277fb69c9622b0ee69547c61121bb36a Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 24 Jul 2023 16:59:42 +0300 Subject: [PATCH 02/95] add blueprints.json and port-app-config.yaml basic example --- .../azure/.port/resources/blueprints.json | 40 +++++++++++++++++++ .../.port/resources/port-app-config.yaml | 19 +++++++++ 2 files changed, 59 insertions(+) create mode 100644 integrations/azure/.port/resources/blueprints.json create mode 100644 integrations/azure/.port/resources/port-app-config.yaml diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json new file mode 100644 index 0000000000..54b358518c --- /dev/null +++ b/integrations/azure/.port/resources/blueprints.json @@ -0,0 +1,40 @@ +[ + { + "identifier": "containerapp", + "description": "This blueprint represents an Azure Container App in our software catalog", + "title": "Container APP", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioning_state": { + "title": "Provisioning State", + "type": "string" + }, + "outbound_ip_addresses": { + "title": "Outbound IP Addresses", + "type": "array" + }, + "external_ingress": { + "title": "External Ingress", + "type": "boolean" + }, + "host_name": { + "title": "Host Name", + "type": "string" + }, + "min_replicas": { + "title": "Min Replicas", + "type": "integer" + }, + "max_replicas": { + "title": "Max Replicas", + "type": "integer" + } + } + } + } +] \ No newline at end of file diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml new file mode 100644 index 0000000000..9acdbd245c --- /dev/null +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -0,0 +1,19 @@ +resources: + - kind: Microsoft.App/containerApps + selector: + # azure resource api version to query + api_version: '2023-07-01' + port: + entity: + mappings: + identifier: .id + title: .name + blueprint: '"containerapp"' + properties: + location: .location + provisioning_state: .properties.provisioningState + outbound_ip_addresses: .properties.outboundIpAddresses + external_ingress: .properties.configuration.ingress.external + host_name: .properties.configuration.ingress.fqdn + min_replicas: .properties.template.scale.minReplicas + max_replicas: .properties.template.scale.maxReplicas From 39d4e7e373c121a3c30e02a0d9c86f570c0ec0dc Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 24 Jul 2023 17:07:42 +0300 Subject: [PATCH 03/95] override default port PortAppConfig to use AzurePortAppConfig --- .../azure/azure_integration/overrides.py | 17 +++++++++++++++++ integrations/azure/integration.py | 8 ++++++++ 2 files changed, 25 insertions(+) create mode 100644 integrations/azure/azure_integration/overrides.py create mode 100644 integrations/azure/integration.py diff --git a/integrations/azure/azure_integration/overrides.py b/integrations/azure/azure_integration/overrides.py new file mode 100644 index 0000000000..35ce68243c --- /dev/null +++ b/integrations/azure/azure_integration/overrides.py @@ -0,0 +1,17 @@ +from port_ocean.core.handlers.port_app_config.models import ( + BaseModel, + ResourceConfig, + PortAppConfig, +) + + +class AzureResourceConfig(ResourceConfig): + class Selector(BaseModel): + query: str = None + api_version: str + + selector: Selector + + +class AzurePortAppConfig(PortAppConfig): + resources: list[AzureResourceConfig] = None diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py new file mode 100644 index 0000000000..d15dc9a3da --- /dev/null +++ b/integrations/azure/integration.py @@ -0,0 +1,8 @@ +from azure_integration.overrides import AzurePortAppConfig +from port_ocean.core.handlers.port_app_config.api import APIPortAppConfig +from port_ocean.core.integrations.base import BaseIntegration + + +class AzureIntegration(BaseIntegration): + class AppConfigHandlerClass(APIPortAppConfig): + CONFIG_CLASS = AzurePortAppConfig From d2cdbacb6dab64e04bb69c6eb78feba11e8c2f50 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 24 Jul 2023 17:40:21 +0300 Subject: [PATCH 04/95] implement `on_resync` & `route` --- .../azure/azure_integration/exceptions.py | 9 ++++ integrations/azure/azure_integration/ocean.py | 45 +++++++++++++++++++ integrations/azure/azure_integration/utils.py | 41 +++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 integrations/azure/azure_integration/exceptions.py create mode 100644 integrations/azure/azure_integration/ocean.py create mode 100644 integrations/azure/azure_integration/utils.py diff --git a/integrations/azure/azure_integration/exceptions.py b/integrations/azure/azure_integration/exceptions.py new file mode 100644 index 0000000000..5e2586e173 --- /dev/null +++ b/integrations/azure/azure_integration/exceptions.py @@ -0,0 +1,9 @@ +from port_ocean.exceptions.base import BaseOceanException + + +class AzureIntegrationException(BaseOceanException): + pass + + +class AzureIntegrationNotFoundKindInPortAppConfig(AzureIntegrationException): + pass diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py new file mode 100644 index 0000000000..bea9c40e0d --- /dev/null +++ b/integrations/azure/azure_integration/ocean.py @@ -0,0 +1,45 @@ +from requests import Request + +from port_ocean.context.ocean import ocean +from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE +from azure.identity import DefaultAzureCredential +from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient + +from utils import ( + get_integration_subscription_id, + get_port_resource_configuration_by_kind, + resolve_resource_type_from_cloud_event, +) + + +@ocean.on_resync() +async def on_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: + resource_client = ResourceManagementClient( + credential=DefaultAzureCredential(), + subscription_id=get_integration_subscription_id(), + ) + async for base_resource in resource_client.resources.list( + filter=f"resourceType eq '{kind}'", + ): + resource_config = get_port_resource_configuration_by_kind(kind) + resource = await resource_client.resources.get_by_id( + resource_id=base_resource.id, + api_version=resource_config["selector"]["api_version"], + ) + yield resource.as_dict() + + +@ocean.router.post("/azure/events") +async def handle_events(request: Request): + event = await request.json() + resource_type = resolve_resource_type_from_cloud_event(event) + resource_config = get_port_resource_configuration_by_kind(resource_type) + resource_client = ResourceManagementClient( + credential=DefaultAzureCredential(), + subscription_id=get_integration_subscription_id(), + ) + resource = await resource_client.resources.get_by_id( + resource_id=event["resourceId"], + api_version=resource_config["selector"]["api_version"], + ) + await ocean.register_raw(resource_type, [resource.as_dict()]) diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py new file mode 100644 index 0000000000..6693af890c --- /dev/null +++ b/integrations/azure/azure_integration/utils.py @@ -0,0 +1,41 @@ +import re + +from port_ocean.context.ocean import ocean +from overrides import AzurePortAppConfig +from exceptions import AzureIntegrationNotFoundKindInPortAppConfig + + +def get_integration_subscription_id() -> str: + logic_settings = ocean.integration_config + return logic_settings["subscriptionId"] + + +def get_port_resource_configuration_by_kind(kind: str) -> dict: + app_config: AzurePortAppConfig = ( + ocean.integration.port_app_config_handler.get_port_app_config() + ) + for resource in app_config.resources: + if resource.kind == kind: + return resource.dict() + raise AzureIntegrationNotFoundKindInPortAppConfig( + f"kind {kind} was not found in port app config" + ) + + +def resolve_resource_type_from_cloud_event(event: dict) -> str: + """ + Resolves the resource type from the cloud event payload + + example of resource_uri in the event payload: + /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/myVM + + pattern: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} + + :param event: Cloud event payload + :return: Resource type + """ + pattern = r"/subscriptions/(?P[^/]+)/resourceGroups/(?P[^/]+)/(?P[^/]+)/(?P.+)/(?P[^/]+)" + resource_uri = event["data"]["resourceUri"] + match = re.match(pattern, resource_uri) + resource_type = match.group("resource_type") + return resource_type From 2f13dceab94e7ce1a1b8608e031cf06cfc298f2e Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 24 Jul 2023 17:40:41 +0300 Subject: [PATCH 05/95] add import for azure integration ocean implementation --- integrations/azure/main.py | 44 +++----------------------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/integrations/azure/main.py b/integrations/azure/main.py index 48f98141e9..25a74944b7 100644 --- a/integrations/azure/main.py +++ b/integrations/azure/main.py @@ -1,41 +1,3 @@ -from typing import Any - -from port_ocean.context.ocean import ocean - - -# Required -# Listen to the resync event of all the kinds specified in the mapping inside port. -# Called each time with a different kind that should be returned from the source system. -@ocean.on_resync() -async def on_resync(kind: str) -> list[dict[Any, Any]]: - # 1. Get all data from the source system - # 2. Return a list of dictionaries with the raw data of the state to run the core logic of the framework for - # Example: - # if kind == "project": - # return [{"some_project_key": "someProjectValue", ...}] - # if kind == "issues": - # return [{"some_issue_key": "someIssueValue", ...}] - return [] - - -# The same sync logic can be registered for one of the kinds that are available in the mapping in port. -# @ocean.on_resync('project') -# async def resync_project(kind: str) -> list[dict[Any, Any]]: -# # 1. Get all projects from the source system -# # 2. Return a list of dictionaries with the raw data of the state -# return [{"some_project_key": "someProjectValue", ...}] -# -# @ocean.on_resync('issues') -# async def resync_issues(kind: str) -> list[dict[Any, Any]]: -# # 1. Get all issues from the source system -# # 2. Return a list of dictionaries with the raw data of the state -# return [{"some_issue_key": "someIssueValue", ...}] - - -# Optional -# Listen to the start event of the integration. Called once when the integration starts. -@ocean.on_start() -async def on_start() -> None: - # Something to do when the integration starts - # For example create a client to query 3rd party services - GitHub, Jira, etc... - print("Starting integration") +# noinspection PyUnresolvedReferences +# ruff: noqa: F401 +from azure_integration import ocean From 75dc0c696da5106b581192b38405a1b19cf1afa7 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 26 Jul 2023 11:30:16 +0300 Subject: [PATCH 06/95] fix containerapp blueprint --- integrations/azure/.port/resources/blueprints.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index 54b358518c..a10a73a14d 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -28,11 +28,11 @@ }, "min_replicas": { "title": "Min Replicas", - "type": "integer" + "type": "number" }, "max_replicas": { "title": "Max Replicas", - "type": "integer" + "type": "number" } } } From 1841fb212ef91f63a1a9702e869a76bcdf7755b8 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 26 Jul 2023 11:30:41 +0300 Subject: [PATCH 07/95] add query to port-app-config --- integrations/azure/.port/resources/.gitignore | 1 - integrations/azure/.port/resources/port-app-config.yaml | 1 + port_ocean/clients/port/mixins/integrations.py | 1 + port_ocean/core/handlers/port_app_config/base.py | 4 ++-- port_ocean/ocean.py | 8 ++++++++ 5 files changed, 12 insertions(+), 3 deletions(-) delete mode 100644 integrations/azure/.port/resources/.gitignore diff --git a/integrations/azure/.port/resources/.gitignore b/integrations/azure/.port/resources/.gitignore deleted file mode 100644 index f935021a8f..0000000000 --- a/integrations/azure/.port/resources/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!.gitignore diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index 9acdbd245c..6d3ecb9835 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -1,6 +1,7 @@ resources: - kind: Microsoft.App/containerApps selector: + query: "true" # azure resource api version to query api_version: '2023-07-01' port: diff --git a/port_ocean/clients/port/mixins/integrations.py b/port_ocean/clients/port/mixins/integrations.py index 7bcc5b4358..e8f5bb0d76 100644 --- a/port_ocean/clients/port/mixins/integrations.py +++ b/port_ocean/clients/port/mixins/integrations.py @@ -42,6 +42,7 @@ async def create_integration( "installationId": self.integration_identifier, "installationAppType": _type, "changelogDestination": changelog_destination, + "config": {}, } if port_app_config: json["config"] = port_app_config.to_request() diff --git a/port_ocean/core/handlers/port_app_config/base.py b/port_ocean/core/handlers/port_app_config/base.py index e02ed8c65f..31fa6809ca 100644 --- a/port_ocean/core/handlers/port_app_config/base.py +++ b/port_ocean/core/handlers/port_app_config/base.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Type, Any +from typing import Any from port_ocean.context.event import event from port_ocean.core.base import BaseWithContext @@ -7,7 +7,7 @@ class BasePortAppConfig(BaseWithContext): - CONFIG_CLASS: Type[PortAppConfig] = PortAppConfig + CONFIG_CLASS: PortAppConfig = PortAppConfig @abstractmethod async def _get_port_app_config(self) -> dict[str, Any]: diff --git a/port_ocean/ocean.py b/port_ocean/ocean.py index ecdf8a68ef..f97c169501 100644 --- a/port_ocean/ocean.py +++ b/port_ocean/ocean.py @@ -2,6 +2,7 @@ from typing import Callable from fastapi import FastAPI, APIRouter +from fastapi.middleware.cors import CORSMiddleware from loguru import logger from pydantic import BaseModel from starlette.types import Scope, Receive, Send @@ -29,6 +30,13 @@ def __init__( ): initialize_port_ocean_context(self) self.fast_api_app = app or FastAPI() + self.fast_api_app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) self.fast_api_app.middleware("http")(request_handler) self.config = IntegrationConfiguration(base_path="./") From 85935c93753242c332b4cec660f0f45547948d99 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 26 Jul 2023 11:31:51 +0300 Subject: [PATCH 08/95] set initializePortResources: true and configure Kafka as eventListener --- integrations/azure/config.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml index c32eb06cd2..6f29040ddd 100644 --- a/integrations/azure/config.yaml +++ b/integrations/azure/config.yaml @@ -1,17 +1,21 @@ # This is an example configuration file for the integration service. # Please copy this file to config.yaml file in the integration folder and edit it to your needs. - +initializePortResources: true port: clientId: {{ from env PORT_CLIENT_ID }} # Can be loaded via environment variable: PORT_CLIENT_ID clientSecret: {{ from env PORT_CLIENT_SECRET }} # Can be loaded via environment variable: PORT_CLIENT_SECRET + baseUrl: http://localhost:3000 # The event listener to use for the integration service. eventListener: +# type: SAMPLE type: KAFKA + brokers: "localhost:9092" + kafkaSecurityEnabled: false integration: # The identifier of this integration instance. identifier: {{ from env INTEGRATION_IDENTIFIER }} # The type of the integration. - type: "My Integration type (Gitlab, Jira, etc.)" + type: "Azure Integration" config: - myGitToken: {{ from env MY_GIT_TOKEN }} + subscriptionId: {{ from env AZURE_SUBSCRIPTION_ID }} someApplicationUrl: "https://I-Am-Not-A-Real-Url.com" \ No newline at end of file From 5d6e80bf74d6d4b8c67f586bc6465d0d8e4db3ad Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 26 Jul 2023 11:32:58 +0300 Subject: [PATCH 09/95] add cloud event middleware handler --- integrations/azure/integration.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py index d15dc9a3da..f3e78781ab 100644 --- a/integrations/azure/integration.py +++ b/integrations/azure/integration.py @@ -1,8 +1,28 @@ -from azure_integration.overrides import AzurePortAppConfig +from requests import Request, Response +from typing import Awaitable, Callable + from port_ocean.core.handlers.port_app_config.api import APIPortAppConfig from port_ocean.core.integrations.base import BaseIntegration +from loguru import logger + +from azure_integration.overrides import AzurePortAppConfig class AzureIntegration(BaseIntegration): class AppConfigHandlerClass(APIPortAppConfig): CONFIG_CLASS = AzurePortAppConfig + + +async def cloud_event_validation_middleware_handler( + request: Request, call_next: Callable[[Request], Awaitable[Response]] +) -> Response: + """ + Middleware used to handle cloud event validation requests + https://github.com/cloudevents/spec/blob/v1.0/http-webhook.md#42-validation-response + """ + response = await call_next(request) + if request.method == "OPTIONS" and request.url.path.startswith("/integration"): + logger.info("Detected cloud event validation request", request=request) + response.headers["WebHook-Allowed-Rate"] = "1000" + response.headers["WebHook-Allowed-Origin"] = "*" + return response From eab56d578ff1654ac19616427ec04ed5b46efd2d Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 26 Jul 2023 11:33:46 +0300 Subject: [PATCH 10/95] add middleware to ocean app --- integrations/azure/main.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/integrations/azure/main.py b/integrations/azure/main.py index 25a74944b7..353921188b 100644 --- a/integrations/azure/main.py +++ b/integrations/azure/main.py @@ -1,3 +1,11 @@ # noinspection PyUnresolvedReferences # ruff: noqa: F401 from azure_integration import ocean +from integration import cloud_event_validation_middleware_handler + +from port_ocean.context.ocean import ocean + + +ocean.app.fast_api_app.middleware("azure_cloud_event")( + cloud_event_validation_middleware_handler +) From 9a047804f13ede07df949599f4eb13bb80484e53 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 26 Jul 2023 11:33:59 +0300 Subject: [PATCH 11/95] add event handler and resync implementation --- integrations/azure/azure_integration/ocean.py | 89 ++++++++++++++----- integrations/azure/azure_integration/utils.py | 32 ++++--- 2 files changed, 81 insertions(+), 40 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index bea9c40e0d..93bac6295c 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -1,11 +1,11 @@ -from requests import Request - +from fastapi import Request +from loguru import logger from port_ocean.context.ocean import ocean from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE -from azure.identity import DefaultAzureCredential +from azure.identity.aio import DefaultAzureCredential from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient -from utils import ( +from azure_integration.utils import ( get_integration_subscription_id, get_port_resource_configuration_by_kind, resolve_resource_type_from_cloud_event, @@ -14,32 +14,75 @@ @ocean.on_resync() async def on_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - resource_client = ResourceManagementClient( + logger.debug("Resyncing", kind=kind) + async with ResourceManagementClient( credential=DefaultAzureCredential(), subscription_id=get_integration_subscription_id(), - ) - async for base_resource in resource_client.resources.list( - filter=f"resourceType eq '{kind}'", - ): - resource_config = get_port_resource_configuration_by_kind(kind) - resource = await resource_client.resources.get_by_id( - resource_id=base_resource.id, - api_version=resource_config["selector"]["api_version"], - ) - yield resource.as_dict() + ) as client: + async for base_resource in client.resources.list( + filter=f"resourceType eq '{kind}'", + ): + logger.debug("Found resource", resource_id=base_resource.id) + resource_config = await get_port_resource_configuration_by_kind(kind) + api_version = resource_config["selector"]["api_version"] + logger.debug( + "Querying full resource", + id=base_resource.id, + kind=kind, + api_version=api_version, + ) + resource = await client.resources.get_by_id( + resource_id=base_resource.id, + api_version=api_version, + ) + + logger.debug("Yielding resource", resource_id=resource.id) + yield resource.as_dict() @ocean.router.post("/azure/events") async def handle_events(request: Request): + """ + Handles System events from Azure Event Grid by the Azure subscription resource and registers them in Port + The event payload is a CloudEvent + https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema + https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema + """ event = await request.json() + logger.debug( + "Received azure cloud event", + event_id=event["id"], + event_type=event["type"], + resource_provider=event["data"]["resourceProvider"], + operation_name=event["data"]["operationName"], + ) resource_type = resolve_resource_type_from_cloud_event(event) - resource_config = get_port_resource_configuration_by_kind(resource_type) - resource_client = ResourceManagementClient( + if not resource_type: + logger.warning( + "Weren't able to resolve resource type from cloud event", + resource_uri=event["data"]["resourceUri"], + ) + return {"ok": False} + resource_config = await get_port_resource_configuration_by_kind(resource_type) + if not resource_config: + logger.warning( + "Resource type not found in port app config", + resource_type=resource_type, + ) + return {"ok": False} + async with ResourceManagementClient( credential=DefaultAzureCredential(), subscription_id=get_integration_subscription_id(), - ) - resource = await resource_client.resources.get_by_id( - resource_id=event["resourceId"], - api_version=resource_config["selector"]["api_version"], - ) - await ocean.register_raw(resource_type, [resource.as_dict()]) + ) as client: + logger.debug( + "Querying full resource", + id=event["data"]["resourceUri"], + kind=resource_type, + api_version=resource_config["selector"]["api_version"], + ) + resource = await client.resources.get_by_id( + resource_id=event["data"]["resourceUri"], + api_version=resource_config["selector"]["api_version"], + ) + await ocean.register_raw(resource_type, [resource.as_dict()]) + return {"ok": True} diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index 6693af890c..47c85a2579 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -1,8 +1,9 @@ -import re +import typing -from port_ocean.context.ocean import ocean -from overrides import AzurePortAppConfig -from exceptions import AzureIntegrationNotFoundKindInPortAppConfig +from port_ocean.context.event import event +from port_ocean.ocean import ocean + +from azure_integration.overrides import AzurePortAppConfig def get_integration_subscription_id() -> str: @@ -10,19 +11,15 @@ def get_integration_subscription_id() -> str: return logic_settings["subscriptionId"] -def get_port_resource_configuration_by_kind(kind: str) -> dict: - app_config: AzurePortAppConfig = ( - ocean.integration.port_app_config_handler.get_port_app_config() - ) +async def get_port_resource_configuration_by_kind(kind: str) -> dict: + app_config = typing.cast(AzurePortAppConfig, event.port_app_config) for resource in app_config.resources: if resource.kind == kind: return resource.dict() - raise AzureIntegrationNotFoundKindInPortAppConfig( - f"kind {kind} was not found in port app config" - ) + return {} -def resolve_resource_type_from_cloud_event(event: dict) -> str: +def resolve_resource_type_from_cloud_event(cloud_event: dict) -> str: """ Resolves the resource type from the cloud event payload @@ -31,11 +28,12 @@ def resolve_resource_type_from_cloud_event(event: dict) -> str: pattern: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} - :param event: Cloud event payload + :param cloud_event: Cloud event payload :return: Resource type """ - pattern = r"/subscriptions/(?P[^/]+)/resourceGroups/(?P[^/]+)/(?P[^/]+)/(?P.+)/(?P[^/]+)" - resource_uri = event["data"]["resourceUri"] - match = re.match(pattern, resource_uri) - resource_type = match.group("resource_type") + resource_uri = cloud_event["data"]["resourceUri"] + resource = resource_uri.split("/") + if len(resource) < 8: + return "" + resource_type = f"{resource[6]}/{resource[7]}" return resource_type From e4b780ef156c16df43b3d432b9eebef9f18ad5aa Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 13:44:15 +0300 Subject: [PATCH 12/95] update .port files --- integrations/azure/.port/defaults/.gitignore | 1 - integrations/azure/.port/resources/port-app-config.yaml | 2 +- integrations/azure/.port/spec.yaml | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 integrations/azure/.port/defaults/.gitignore diff --git a/integrations/azure/.port/defaults/.gitignore b/integrations/azure/.port/defaults/.gitignore deleted file mode 100644 index f935021a8f..0000000000 --- a/integrations/azure/.port/defaults/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!.gitignore diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index 6d3ecb9835..abb8eed8a3 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -3,7 +3,7 @@ resources: selector: query: "true" # azure resource api version to query - api_version: '2023-07-01' + api_version: '2022-03-01' port: entity: mappings: diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index a2aa2b2d7d..365d9303d1 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,10 +1,10 @@ -version: v0.1.0 -type: gitlab +version: v0.1.0rc1 +type: azure description: azure integration for Port Ocean icon: Cookiecutter features: - type: exporter - section: Git Providers + section: Azure Providers resources: - kind: - kind: From f69354add060ba3610a2ad4c67de1629bb726b41 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 13:45:03 +0300 Subject: [PATCH 13/95] add azure patch --- .../azure/azure_integration/azure_patch.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 integrations/azure/azure_integration/azure_patch.py diff --git a/integrations/azure/azure_integration/azure_patch.py b/integrations/azure/azure_integration/azure_patch.py new file mode 100644 index 0000000000..8503135fbf --- /dev/null +++ b/integrations/azure/azure_integration/azure_patch.py @@ -0,0 +1,88 @@ +from typing import Any, Optional + +import azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations +from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient +from azure.core.rest import HttpRequest +from azure.mgmt.resource.resources.v2022_09_01.operations._operations import ( + _format_url_section, + _SERIALIZER, +) +from loguru import logger + + +def build_full_resources_list_request_patch( + subscription_id: str, + *, + filter: Optional[str] = None, + expand: Optional[str] = None, + top: Optional[int] = None, + **kwargs: Any, +) -> HttpRequest: + """ + Builds the request for the resources list request that will query the resource provider instead of the resources api + + The original request that is being built is: + GET https://management.azure.com/subscriptions/{subscriptionId}/resources?api-version={apiVersion} + The original request is querying the resources api which returns a list of resources with only the resource ID and + resource type. + + The request that we want to build is: + GET https://management.azure.com/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion} + The request is querying the resource provider which returns a list of resources with all the properties. + + The resource type and api version are passed as headers to the request and are popped from the headers before the + request is built. + This is done because there is no way to pass the resource type and api version to the request builder due to the + way the request builder is being called inside the list method. + """ + # Build the original request and the HttpRequest object + request: HttpRequest = old_build_resources_list_request( + subscription_id=subscription_id, filter=filter, expand=expand, top=top, **kwargs + ) + + resource_type = request.headers.pop("resource-type", None) + if resource_type: + api_version = request.headers.pop("api-version", None) + # Build the url + url = "/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion}" + path_format_arguments = { + "subscriptionId": _SERIALIZER.url( + "subscription_id", subscription_id, "str", min_length=1 + ), + "resourceType": _SERIALIZER.url("resource-type", resource_type, "str"), + "apiVersion": _SERIALIZER.url("api-version", api_version, "str"), + } + # Format the url + url = _format_url_section(url, **path_format_arguments) + # Override the original url in the request + request.url = url + return request + + +async def list_resources( + resources_client: ResourceManagementClient, resource_type: str, api_version: str +): + """ + A list implementation that takes advantage of the patch implemented in this file. + To be able to use this implementation, the resource type and api version must be passed as headers to the request. + """ + # override the default version in the client to the version that we want to query + resources_client.resources._config.api_version = api_version + async for resource in resources_client.resources.list( + headers={"resource-type": resource_type, "api-version": api_version} + ): + logger.debug( + "Found resource", + resource_id=resource.id, + kind=resource_type, + api_version=api_version, + ) + yield resource + + +old_build_resources_list_request = ( + azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request +) +azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request = ( + build_full_resources_list_request_patch +) From de634437f520514d24e69f798878726a07fd6b3b Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 13:45:57 +0300 Subject: [PATCH 14/95] adjust logic to use the azure patch --- integrations/azure/azure_integration/ocean.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index 93bac6295c..f8b0b58d20 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -3,6 +3,7 @@ from port_ocean.context.ocean import ocean from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE from azure.identity.aio import DefaultAzureCredential +from azure.core.exceptions import ResourceNotFoundError from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient from azure_integration.utils import ( @@ -10,34 +11,22 @@ get_port_resource_configuration_by_kind, resolve_resource_type_from_cloud_event, ) +from azure_integration.azure_patch import list_resources @ocean.on_resync() async def on_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: logger.debug("Resyncing", kind=kind) - async with ResourceManagementClient( - credential=DefaultAzureCredential(), - subscription_id=get_integration_subscription_id(), - ) as client: - async for base_resource in client.resources.list( - filter=f"resourceType eq '{kind}'", - ): - logger.debug("Found resource", resource_id=base_resource.id) - resource_config = await get_port_resource_configuration_by_kind(kind) - api_version = resource_config["selector"]["api_version"] - logger.debug( - "Querying full resource", - id=base_resource.id, - kind=kind, - api_version=api_version, - ) - resource = await client.resources.get_by_id( - resource_id=base_resource.id, - api_version=api_version, - ) - logger.debug("Yielding resource", resource_id=resource.id) - yield resource.as_dict() + resource_config = await get_port_resource_configuration_by_kind(kind) + api_version = resource_config["selector"]["api_version"] + async with DefaultAzureCredential() as credential: + async with ResourceManagementClient( + credential=credential, subscription_id=get_integration_subscription_id() + ) as client: + logger.debug("Listing resources", kind=kind, api_version=api_version) + async for resource in list_resources(client, kind, api_version): + yield resource.as_dict() @ocean.router.post("/azure/events") @@ -70,19 +59,30 @@ async def handle_events(request: Request): resource_type=resource_type, ) return {"ok": False} - async with ResourceManagementClient( - credential=DefaultAzureCredential(), - subscription_id=get_integration_subscription_id(), - ) as client: - logger.debug( - "Querying full resource", - id=event["data"]["resourceUri"], - kind=resource_type, - api_version=resource_config["selector"]["api_version"], - ) - resource = await client.resources.get_by_id( - resource_id=event["data"]["resourceUri"], - api_version=resource_config["selector"]["api_version"], - ) - await ocean.register_raw(resource_type, [resource.as_dict()]) + + async with DefaultAzureCredential() as credential: + async with ResourceManagementClient( + credential=credential, subscription_id=get_integration_subscription_id() + ) as client: + logger.debug( + "Querying full resource", + id=event["data"]["resourceUri"], + kind=resource_type, + api_version=resource_config["selector"]["api_version"], + ) + try: + resource = await client.resources.get_by_id( + resource_id=event["data"]["resourceUri"], + api_version=resource_config["selector"]["api_version"], + ) + except ResourceNotFoundError: + # TODO: delete from port once ocean adds support to delete per identifier + logger.warning( + "Resource not found", + id=event["data"]["resourceUri"], + kind=resource_type, + api_version=resource_config["selector"]["api_version"], + ) + return {"ok": False} + await ocean.register_raw(resource_type, [resource.as_dict()]) return {"ok": True} From 2d721742261c795febc50dce0045ed4b98f2bfbc Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 13:46:12 +0300 Subject: [PATCH 15/95] add dependencies --- integrations/azure/config.yaml | 6 +++--- integrations/azure/poetry.lock | 26 +++++++++++++++++++++----- integrations/azure/pyproject.toml | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml index 6f29040ddd..31b1bd7970 100644 --- a/integrations/azure/config.yaml +++ b/integrations/azure/config.yaml @@ -2,9 +2,9 @@ # Please copy this file to config.yaml file in the integration folder and edit it to your needs. initializePortResources: true port: - clientId: {{ from env PORT_CLIENT_ID }} # Can be loaded via environment variable: PORT_CLIENT_ID - clientSecret: {{ from env PORT_CLIENT_SECRET }} # Can be loaded via environment variable: PORT_CLIENT_SECRET - baseUrl: http://localhost:3000 + clientId: {{ from env PORT_CLIENT_ID }} # Can be loaded via environment variable: PORT_CLIENT_ID, if both are set, the environment variable will be used. + clientSecret: {{ from env PORT_CLIENT_SECRET }} # Can be loaded via environment variable: PORT_CLIENT_SECRET, if both are set, the environment variable will be used. + baseUrl: http://localhost:3000 # Can be loaded via environment variable: PORT_BASE_URL, if both are set, the environment variable will be used. # The event listener to use for the integration service. eventListener: # type: SAMPLE diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock index 65884909c6..113c0d74d1 100644 --- a/integrations/azure/poetry.lock +++ b/integrations/azure/poetry.lock @@ -910,6 +910,21 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jinja2-time" +version = "0.2.0" +description = "Jinja2 Extension for Dates and Times" +optional = false +python-versions = "*" +files = [ + {file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"}, + {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"}, +] + +[package.dependencies] +arrow = "*" +jinja2 = "*" + [[package]] name = "lazy-object-proxy" version = "1.9.0" @@ -1308,13 +1323,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "port-ocean" -version = "0.1.0" +version = "0.1.1" description = "Port Ocean is a CLI tool for managing your Port projects." optional = false python-versions = ">=3.11,<4.0" files = [ - {file = "port_ocean-0.1.0-py3-none-any.whl", hash = "sha256:cbbac3a1caa2700ab0fe05c0d2cb8575825f740152b992a44b8126e9a2f49720"}, - {file = "port_ocean-0.1.0.tar.gz", hash = "sha256:688d97988a452eac981855bc0e41029f758084e4f3061f7bd50808a172964e87"}, + {file = "port_ocean-0.1.1-py3-none-any.whl", hash = "sha256:be798394721fde820b44e01fcb94fcaded376c751d5f0676ac746c9a38b69f19"}, + {file = "port_ocean-0.1.1.tar.gz", hash = "sha256:bf24e7b21f49aa27c7f8bd98905d89297bd545cf704b556140116175ef964e4b"}, ] [package.dependencies] @@ -1323,6 +1338,7 @@ confluent-kafka = ">=2.1.1,<3.0.0" cookiecutter = {version = ">=2.1.1,<3.0.0", optional = true, markers = "extra == \"cli\""} fastapi = ">=0.100.0,<0.101.0" httpx = ">=0.24.1,<0.25.0" +jinja2-time = {version = ">=0.2.0,<0.3.0", optional = true, markers = "extra == \"cli\""} loguru = ">=0.7.0,<0.8.0" pydantic = ">=1.10.8,<2.0.0" pyjq = ">=2.6.0,<3.0.0" @@ -1334,7 +1350,7 @@ uvicorn = ">=0.22.0,<0.23.0" werkzeug = ">=2.3.4,<3.0.0" [package.extras] -cli = ["click (>=8.1.3,<9.0.0)", "cookiecutter (>=2.1.1,<3.0.0)", "rich (>=13.4.1,<14.0.0)"] +cli = ["click (>=8.1.3,<9.0.0)", "cookiecutter (>=2.1.1,<3.0.0)", "jinja2-time (>=0.2.0,<0.3.0)", "rich (>=13.4.1,<14.0.0)"] [[package]] name = "portalocker" @@ -2006,4 +2022,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "0e99598fe2708ff9825b384b52b042838748c113f6b2b0175095e25928225bce" +content-hash = "559386de00bd25436eb7191fa9359ece6fc473e3d5ae81f3e3ac3d473ab8354f" diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml index 1bfa825ab9..d94c729aac 100644 --- a/integrations/azure/pyproject.toml +++ b/integrations/azure/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Tom Tankilevitch "] [tool.poetry.dependencies] python = "^3.11" -port_ocean = { version = "^0.1.0", extras = ["cli"] } +port_ocean = {version = "0.1.1", extras = ["cli"]} azure-mgmt-resource = "^23.0.1" azure-identity = "^1.13.0" aiohttp = "^3.7.4" From 6a659bef72d2d8066588fb965181427164deaed6 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 13:48:42 +0300 Subject: [PATCH 16/95] handle events from port where entities are being deleted --- .../core/event_listener/kafka/event_listener.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/port_ocean/core/event_listener/kafka/event_listener.py b/port_ocean/core/event_listener/kafka/event_listener.py index 1180428857..072ecf1c46 100644 --- a/port_ocean/core/event_listener/kafka/event_listener.py +++ b/port_ocean/core/event_listener/kafka/event_listener.py @@ -55,10 +55,13 @@ async def _get_kafka_config(self) -> KafkaConsumerConfig: return KafkaConsumerConfig.parse_obj(self.event_listener_config.dict()) - def should_be_processed(self, msg_value: dict[Any, Any], topic: str) -> bool: - integration_identifier = ( - msg_value.get("diff", {}).get("after", {}).get("identifier") - ) + def _should_be_processed(self, msg_value: dict[Any, Any], topic: str) -> bool: + after = msg_value.get("diff", {}).get("after", {}) + # handles delete events where there is no after + if after is None: + return False + + integration_identifier = after.get("identifier") if integration_identifier == self.integration_identifier and ( "change.log" in topic ): @@ -67,7 +70,7 @@ def should_be_processed(self, msg_value: dict[Any, Any], topic: str) -> bool: return False async def _handle_message(self, message: dict[Any, Any], topic: str) -> None: - if not self.should_be_processed(message, topic): + if not self._should_be_processed(message, topic): return if "change.log" in topic and message is not None: From bac677e641841038dead72c42b4dabfd5c720ff8 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 16:40:19 +0300 Subject: [PATCH 17/95] fix to use snake_case --- integrations/azure/azure_integration/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index 47c85a2579..40bb15244e 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -8,7 +8,7 @@ def get_integration_subscription_id() -> str: logic_settings = ocean.integration_config - return logic_settings["subscriptionId"] + return logic_settings["subscription_id"] async def get_port_resource_configuration_by_kind(kind: str) -> dict: From 8ef3ee9681381876938620b2970a8f49ef8f2a38 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 17:00:42 +0300 Subject: [PATCH 18/95] comment out to support envs --- integrations/azure/config.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml index 31b1bd7970..69e8162cb4 100644 --- a/integrations/azure/config.yaml +++ b/integrations/azure/config.yaml @@ -4,13 +4,19 @@ initializePortResources: true port: clientId: {{ from env PORT_CLIENT_ID }} # Can be loaded via environment variable: PORT_CLIENT_ID, if both are set, the environment variable will be used. clientSecret: {{ from env PORT_CLIENT_SECRET }} # Can be loaded via environment variable: PORT_CLIENT_SECRET, if both are set, the environment variable will be used. - baseUrl: http://localhost:3000 # Can be loaded via environment variable: PORT_BASE_URL, if both are set, the environment variable will be used. + baseUrl: {{ from env PORT_BASE_URL }} +# baseUrl: http://localhost:3000 # Can be loaded via environment variable: PORT_BASE_URL, if both are set, the environment variable will be used. # The event listener to use for the integration service. eventListener: # type: SAMPLE type: KAFKA - brokers: "localhost:9092" - kafkaSecurityEnabled: false + brokers: {{ from env EVENT_LISTENER_BROKERS }} +# username: {{ from env EVENT_LISTENER_USERNAME }} +# password: {{ from env EVENT_LISTENER_PASSWORD }} +# consumerGroup: {{ from env EVENT_LISTENER_CONSUMER_GROUP }} +# authenticationMechanism: {{ from env EVENT_LISTENER_AUTHENTICATION_MECHANISM }} +# kafkaSecurityEnabled: true +# brokers: "localhost:9092" integration: # The identifier of this integration instance. identifier: {{ from env INTEGRATION_IDENTIFIER }} From 82615751c89e8ff0a35d3357a1be9262f58da8fd Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 18:10:10 +0300 Subject: [PATCH 19/95] bump to rc2 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 365d9303d1..fd7931d65e 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc1 +version: v0.1.0rc2 type: azure description: azure integration for Port Ocean icon: Cookiecutter From 64abece10ab930e451ecda3d6750afa198862fce Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 18:22:47 +0300 Subject: [PATCH 20/95] support old and new configuration --- integrations/azure/azure_integration/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index 40bb15244e..65ddf62fc8 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -8,7 +8,11 @@ def get_integration_subscription_id() -> str: logic_settings = ocean.integration_config - return logic_settings["subscription_id"] + # TODO: change once main branch is released as 0.1.3 + subscription_id = logic_settings.get("subscription_id", "") or logic_settings.get( + "subscriptionId", "" + ) + return subscription_id async def get_port_resource_configuration_by_kind(kind: str) -> dict: From b73c7fe0bf510576399e5bbb15d3921e97fabdc2 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 18:22:56 +0300 Subject: [PATCH 21/95] bump rc3 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index fd7931d65e..5f47b42ad6 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc2 +version: v0.1.0rc3 type: azure description: azure integration for Port Ocean icon: Cookiecutter From c589d05c692e0156b24dfe8d8b2430333f85eaec Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 19:00:29 +0300 Subject: [PATCH 22/95] add logs --- port_ocean/consumers/kafka_consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port_ocean/consumers/kafka_consumer.py b/port_ocean/consumers/kafka_consumer.py index bf15448730..7bab10b920 100644 --- a/port_ocean/consumers/kafka_consumer.py +++ b/port_ocean/consumers/kafka_consumer.py @@ -53,7 +53,7 @@ def __init__( "group.id": "no-security", "enable.auto.commit": "false", } - + logger.debug(f"Kafka config", kafka_config=kafka_config) self.consumer = Consumer(kafka_config) def _handle_message(self, raw_msg: Message) -> None: From 10793712feae5938103917e9bfc74db1ceb1b7bb Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 19:01:02 +0300 Subject: [PATCH 23/95] rc4 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 5f47b42ad6..2628eaf9fe 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc3 +version: v0.1.0rc4 type: azure description: azure integration for Port Ocean icon: Cookiecutter From ce968298c79649c71e865bd838dea8c86bb4ce6d Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 22:16:24 +0300 Subject: [PATCH 24/95] don't log the whole request --- integrations/azure/integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py index f3e78781ab..fb72a82adc 100644 --- a/integrations/azure/integration.py +++ b/integrations/azure/integration.py @@ -22,7 +22,7 @@ async def cloud_event_validation_middleware_handler( """ response = await call_next(request) if request.method == "OPTIONS" and request.url.path.startswith("/integration"): - logger.info("Detected cloud event validation request", request=request) + logger.info("Detected cloud event validation request") response.headers["WebHook-Allowed-Rate"] = "1000" response.headers["WebHook-Allowed-Origin"] = "*" return response From 599f7593b3a32e43ec24f877aef11dbbc0890659 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 27 Jul 2023 22:16:38 +0300 Subject: [PATCH 25/95] bump rc5 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 2628eaf9fe..5903510911 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc4 +version: v0.1.0rc5 type: azure description: azure integration for Port Ocean icon: Cookiecutter From d869c4a96d8ff33039c9b289419075919316157b Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Fri, 28 Jul 2023 20:40:30 +0300 Subject: [PATCH 26/95] update to debug logger --- integrations/azure/azure_integration/ocean.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index f8b0b58d20..4869583436 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -54,8 +54,8 @@ async def handle_events(request: Request): return {"ok": False} resource_config = await get_port_resource_configuration_by_kind(resource_type) if not resource_config: - logger.warning( - "Resource type not found in port app config", + logger.debug( + "Resource type not found in port app config, update port app config to include the resource type", resource_type=resource_type, ) return {"ok": False} From 6adb0692d5016e09b3115b2b26c57fd2c2168939 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Fri, 28 Jul 2023 20:48:48 +0300 Subject: [PATCH 27/95] update query --- integrations/azure/azure_integration/overrides.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/azure_integration/overrides.py b/integrations/azure/azure_integration/overrides.py index 35ce68243c..7b04edcd69 100644 --- a/integrations/azure/azure_integration/overrides.py +++ b/integrations/azure/azure_integration/overrides.py @@ -7,7 +7,7 @@ class AzureResourceConfig(ResourceConfig): class Selector(BaseModel): - query: str = None + query: str api_version: str selector: Selector From 70b0cae985666cd5959764d19e1527c0a787a43d Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Fri, 28 Jul 2023 21:01:04 +0300 Subject: [PATCH 28/95] update requirements --- integrations/azure/poetry.lock | 20 ++++++++++++++++---- integrations/azure/pyproject.toml | 5 +++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock index 113c0d74d1..1eeb5fb6b6 100644 --- a/integrations/azure/poetry.lock +++ b/integrations/azure/poetry.lock @@ -1323,13 +1323,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "port-ocean" -version = "0.1.1" +version = "0.1.2" description = "Port Ocean is a CLI tool for managing your Port projects." optional = false python-versions = ">=3.11,<4.0" files = [ - {file = "port_ocean-0.1.1-py3-none-any.whl", hash = "sha256:be798394721fde820b44e01fcb94fcaded376c751d5f0676ac746c9a38b69f19"}, - {file = "port_ocean-0.1.1.tar.gz", hash = "sha256:bf24e7b21f49aa27c7f8bd98905d89297bd545cf704b556140116175ef964e4b"}, + {file = "port_ocean-0.1.2-py3-none-any.whl", hash = "sha256:1fa323db88f64cf97afe34baa0b498f02c75d79ae9d7e82487258c81c65ecd79"}, + {file = "port_ocean-0.1.2.tar.gz", hash = "sha256:d1b94607b958e9fe027d55c8175d198172ebe2199aa66fce7f6886f67115fc1b"}, ] [package.dependencies] @@ -1341,6 +1341,7 @@ httpx = ">=0.24.1,<0.25.0" jinja2-time = {version = ">=0.2.0,<0.3.0", optional = true, markers = "extra == \"cli\""} loguru = ">=0.7.0,<0.8.0" pydantic = ">=1.10.8,<2.0.0" +pyhumps = ">=3.8.0,<4.0.0" pyjq = ">=2.6.0,<3.0.0" pyyaml = ">=6.0,<7.0" rich = {version = ">=13.4.1,<14.0.0", optional = true, markers = "extra == \"cli\""} @@ -1448,6 +1449,17 @@ files = [ [package.extras] plugins = ["importlib-metadata"] +[[package]] +name = "pyhumps" +version = "3.8.0" +description = "🐫 Convert strings (and dictionary keys) between snake case, camel case and pascal case in Python. Inspired by Humps for Node" +optional = false +python-versions = "*" +files = [ + {file = "pyhumps-3.8.0-py3-none-any.whl", hash = "sha256:060e1954d9069f428232a1adda165db0b9d8dfdce1d265d36df7fbff540acfd6"}, + {file = "pyhumps-3.8.0.tar.gz", hash = "sha256:498026258f7ee1a8e447c2e28526c0bea9407f9a59c03260aee4bd6c04d681a3"}, +] + [[package]] name = "pyjq" version = "2.6.0" @@ -2022,4 +2034,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "559386de00bd25436eb7191fa9359ece6fc473e3d5ae81f3e3ac3d473ab8354f" +content-hash = "448f9b57713f255eeff2bed21a3ad5251db87d928f4b573596ef4a4348d377f1" diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml index d94c729aac..9abfd737a2 100644 --- a/integrations/azure/pyproject.toml +++ b/integrations/azure/pyproject.toml @@ -6,8 +6,9 @@ authors = ["Tom Tankilevitch "] [tool.poetry.dependencies] python = "^3.11" -port_ocean = {version = "0.1.1", extras = ["cli"]} -azure-mgmt-resource = "^23.0.1" +port_ocean = {version = ">=0.1.2,<0.2.0", extras = ["cli"]} +# due to patching the azure-mgmt-resource package, we need to use a specific version +azure-mgmt-resource = "23.0.1" azure-identity = "^1.13.0" aiohttp = "^3.7.4" From cbca07210c36c754917e3bde81d1163f5d2c882e Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Fri, 28 Jul 2023 21:57:29 +0300 Subject: [PATCH 29/95] fix middleware --- integrations/azure/integration.py | 15 ++++++++++----- port_ocean/consumers/kafka_consumer.py | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py index fb72a82adc..b822c4a4b9 100644 --- a/integrations/azure/integration.py +++ b/integrations/azure/integration.py @@ -1,9 +1,10 @@ from requests import Request, Response from typing import Awaitable, Callable +import fastapi +from loguru import logger from port_ocean.core.handlers.port_app_config.api import APIPortAppConfig from port_ocean.core.integrations.base import BaseIntegration -from loguru import logger from azure_integration.overrides import AzurePortAppConfig @@ -20,9 +21,13 @@ async def cloud_event_validation_middleware_handler( Middleware used to handle cloud event validation requests https://github.com/cloudevents/spec/blob/v1.0/http-webhook.md#42-validation-response """ - response = await call_next(request) if request.method == "OPTIONS" and request.url.path.startswith("/integration"): logger.info("Detected cloud event validation request") - response.headers["WebHook-Allowed-Rate"] = "1000" - response.headers["WebHook-Allowed-Origin"] = "*" - return response + headers = { + "WebHook-Allowed-Rate": "1000", + "WebHook-Allowed-Origin": "*", + } + response = fastapi.Response(status_code=200, headers=headers) + return response + + return await call_next(request) diff --git a/port_ocean/consumers/kafka_consumer.py b/port_ocean/consumers/kafka_consumer.py index 7bab10b920..7b033ff86c 100644 --- a/port_ocean/consumers/kafka_consumer.py +++ b/port_ocean/consumers/kafka_consumer.py @@ -53,6 +53,7 @@ def __init__( "group.id": "no-security", "enable.auto.commit": "false", } + # TODO: remove debug logger.debug(f"Kafka config", kafka_config=kafka_config) self.consumer = Consumer(kafka_config) From b433bddc8dfcb81895441ad8cad8349ed88db884 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Fri, 28 Jul 2023 22:03:30 +0300 Subject: [PATCH 30/95] bump rc6 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 5903510911..af0a04cf3f 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc5 +version: v0.1.0rc6 type: azure description: azure integration for Port Ocean icon: Cookiecutter From 7930f5e2a5ad7b88882795e506b848cdd398835f Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 6 Aug 2023 11:45:38 +0300 Subject: [PATCH 31/95] fix --- integrations/azure/azure_integration/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index 65ddf62fc8..1d2853274e 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -8,10 +8,7 @@ def get_integration_subscription_id() -> str: logic_settings = ocean.integration_config - # TODO: change once main branch is released as 0.1.3 - subscription_id = logic_settings.get("subscription_id", "") or logic_settings.get( - "subscriptionId", "" - ) + subscription_id = logic_settings.get("subscription_id", "") return subscription_id From aa3110f3b9c6e4515413517507e22ef660135065 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 7 Aug 2023 13:17:10 +0300 Subject: [PATCH 32/95] update requirements adjust config.yaml bumprc7 --- integrations/azure/.port/spec.yaml | 2 +- integrations/azure/config.yaml | 14 +- integrations/azure/poetry.lock | 201 +++++++++++++++-------------- 3 files changed, 111 insertions(+), 106 deletions(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index af0a04cf3f..2507cdd93f 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc6 +version: v0.1.0rc7 type: azure description: azure integration for Port Ocean icon: Cookiecutter diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml index 69e8162cb4..68cd0dbc19 100644 --- a/integrations/azure/config.yaml +++ b/integrations/azure/config.yaml @@ -2,15 +2,15 @@ # Please copy this file to config.yaml file in the integration folder and edit it to your needs. initializePortResources: true port: - clientId: {{ from env PORT_CLIENT_ID }} # Can be loaded via environment variable: PORT_CLIENT_ID, if both are set, the environment variable will be used. - clientSecret: {{ from env PORT_CLIENT_SECRET }} # Can be loaded via environment variable: PORT_CLIENT_SECRET, if both are set, the environment variable will be used. - baseUrl: {{ from env PORT_BASE_URL }} + clientId: "{{ from env PORT_CLIENT_ID }}" # Can be loaded via environment variable: PORT_CLIENT_ID, if both are set, the environment variable will be used. + clientSecret: "{{ from env PORT_CLIENT_SECRET }}" # Can be loaded via environment variable: PORT_CLIENT_SECRET, if both are set, the environment variable will be used. + baseUrl: "{{ from env PORT_BASE_URL }}" # baseUrl: http://localhost:3000 # Can be loaded via environment variable: PORT_BASE_URL, if both are set, the environment variable will be used. # The event listener to use for the integration service. eventListener: # type: SAMPLE - type: KAFKA - brokers: {{ from env EVENT_LISTENER_BROKERS }} + type: POLLING +# brokers: {{ from env EVENT_LISTENER_BROKERS }} # username: {{ from env EVENT_LISTENER_USERNAME }} # password: {{ from env EVENT_LISTENER_PASSWORD }} # consumerGroup: {{ from env EVENT_LISTENER_CONSUMER_GROUP }} @@ -19,9 +19,9 @@ eventListener: # brokers: "localhost:9092" integration: # The identifier of this integration instance. - identifier: {{ from env INTEGRATION_IDENTIFIER }} + identifier: "{{ from env INTEGRATION_IDENTIFIER }}" # The type of the integration. type: "Azure Integration" config: - subscriptionId: {{ from env AZURE_SUBSCRIPTION_ID }} + subscriptionId: "{{ from env AZURE_SUBSCRIPTION_ID }}" someApplicationUrl: "https://I-Am-Not-A-Real-Url.com" \ No newline at end of file diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock index 1eeb5fb6b6..1623dd33df 100644 --- a/integrations/azure/poetry.lock +++ b/integrations/azure/poetry.lock @@ -425,13 +425,13 @@ pycparser = "*" [[package]] name = "chardet" -version = "5.1.0" +version = "5.2.0" description = "Universal encoding detector for Python 3" optional = false python-versions = ">=3.7" files = [ - {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, - {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, ] [[package]] @@ -534,17 +534,21 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "click-default-group" -version = "1.2.2" -description = "Extends click.Group to invoke a command without explicit subcommand name" +version = "1.2.4" +description = "click_default_group" optional = false -python-versions = "*" +python-versions = ">=2.7" files = [ - {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, + {file = "click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f"}, + {file = "click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"}, ] [package.dependencies] click = "*" +[package.extras] +test = ["pytest"] + [[package]] name = "colorama" version = "0.4.6" @@ -604,13 +608,13 @@ schema-registry = ["requests"] [[package]] name = "cookiecutter" -version = "2.2.3" +version = "2.3.0" description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." optional = false python-versions = ">=3.7" files = [ - {file = "cookiecutter-2.2.3-py3-none-any.whl", hash = "sha256:17ad6751aef0a39d004c5ecacbd18176de6e83505343073fd7e48b60bdac5254"}, - {file = "cookiecutter-2.2.3.tar.gz", hash = "sha256:d56f18c0c01c09804450b501ac43e8f6104cfa7cdd93610359c68b1ba9fd84d2"}, + {file = "cookiecutter-2.3.0-py3-none-any.whl", hash = "sha256:7e87944757c6e9f8729cf89a4139b6a35ab4d6dcbc6ae3e7d6360d44ad3ad383"}, + {file = "cookiecutter-2.3.0.tar.gz", hash = "sha256:942a794981747f6d7f439d6e49d39dc91a9a641283614160c93c474c72c29621"}, ] [package.dependencies] @@ -621,37 +625,38 @@ Jinja2 = ">=2.7,<4.0.0" python-slugify = ">=4.0.0" pyyaml = ">=5.3.1" requests = ">=2.23.0" +rich = "*" [[package]] name = "cryptography" -version = "41.0.2" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, - {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, - {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, - {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, ] [package.dependencies] @@ -683,13 +688,13 @@ graph = ["objgraph (>=1.7.2)"] [[package]] name = "fastapi" -version = "0.100.0" +version = "0.100.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.7" files = [ - {file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"}, - {file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"}, + {file = "fastapi-0.100.1-py3-none-any.whl", hash = "sha256:ec6dd52bfc4eff3063cfcd0713b43c87640fefb2687bbbe3d8a08d94049cdf32"}, + {file = "fastapi-0.100.1.tar.gz", hash = "sha256:522700d7a469e4a973d92321ab93312448fbe20fca9c8da97effc7e7bc56df23"}, ] [package.dependencies] @@ -1282,29 +1287,29 @@ files = [ [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" @@ -1323,13 +1328,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "port-ocean" -version = "0.1.2" +version = "0.1.3" description = "Port Ocean is a CLI tool for managing your Port projects." optional = false python-versions = ">=3.11,<4.0" files = [ - {file = "port_ocean-0.1.2-py3-none-any.whl", hash = "sha256:1fa323db88f64cf97afe34baa0b498f02c75d79ae9d7e82487258c81c65ecd79"}, - {file = "port_ocean-0.1.2.tar.gz", hash = "sha256:d1b94607b958e9fe027d55c8175d198172ebe2199aa66fce7f6886f67115fc1b"}, + {file = "port_ocean-0.1.3-py3-none-any.whl", hash = "sha256:c4a02ab9b3e867c53b615aaeff02068ac06a3e8b449cffc962a4a38bba694a8f"}, + {file = "port_ocean-0.1.3.tar.gz", hash = "sha256:3ae6bd92a0da31f678b6f6157ce8a9364daebf24a0d7e683adffa338adba7330"}, ] [package.dependencies] @@ -1385,47 +1390,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.11" +version = "1.10.12" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, - {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, - {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, - {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, - {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, - {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, - {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, - {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, - {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, - {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, ] [package.dependencies] @@ -1437,13 +1442,13 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -1493,17 +1498,17 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" -version = "2.17.4" +version = "2.17.5" description = "python code static checker" optional = false python-versions = ">=3.7.2" files = [ - {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, - {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, + {file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"}, + {file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"}, ] [package.dependencies] -astroid = ">=2.15.4,<=2.17.0-dev0" +astroid = ">=2.15.6,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} isort = ">=4.2.5,<6" @@ -1661,13 +1666,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.4.2" +version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, - {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, + {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, ] [package.dependencies] @@ -1755,13 +1760,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.11.8" +version = "0.12.1" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, - {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, ] [[package]] From cdcb09e606ff38f6ff8f8d049eb36e65d4a99c80 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Tue, 8 Aug 2023 16:28:02 +0300 Subject: [PATCH 33/95] add terraform base implementation for azure --- .../terraform/azure/containerapp/main.tf | 33 +++++ .../modules/authorization/main.tf | 31 ++++ .../modules/authorization/outputs.tf | 3 + .../modules/authorization/variables.tf | 41 ++++++ .../modules/container_app/main.tf | 102 +++++++++++++ .../modules/container_app/outputs.tf | 11 ++ .../modules/container_app/variables.tf | 123 ++++++++++++++++ .../terraform/azure/containerapp/providers.tf | 27 ++++ .../terraform/azure/containerapp/variables.tf | 138 ++++++++++++++++++ 9 files changed, 509 insertions(+) create mode 100644 deployment/terraform/azure/containerapp/main.tf create mode 100644 deployment/terraform/azure/containerapp/modules/authorization/main.tf create mode 100644 deployment/terraform/azure/containerapp/modules/authorization/outputs.tf create mode 100644 deployment/terraform/azure/containerapp/modules/authorization/variables.tf create mode 100644 deployment/terraform/azure/containerapp/modules/container_app/main.tf create mode 100644 deployment/terraform/azure/containerapp/modules/container_app/outputs.tf create mode 100644 deployment/terraform/azure/containerapp/modules/container_app/variables.tf create mode 100644 deployment/terraform/azure/containerapp/providers.tf create mode 100644 deployment/terraform/azure/containerapp/variables.tf diff --git a/deployment/terraform/azure/containerapp/main.tf b/deployment/terraform/azure/containerapp/main.tf new file mode 100644 index 0000000000..f94d7f15a4 --- /dev/null +++ b/deployment/terraform/azure/containerapp/main.tf @@ -0,0 +1,33 @@ +locals { + prefix = "port-ocean" +} + +resource "azurerm_resource_group" "ocean-rg" { + count = var.resource_group_name != null ? 0 : 1 + name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-rg" + location = var.location +} + +module "port_ocean_authorization" { + source = "./modules/authorization" + location = var.location + resource_group_name = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name + integration = var.integration + permissions = var.permissions + subscription_id = var.subscription_id +} + +module "port_ocean_container_app" { + source= "./modules/container_app" + integration = var.integration + port = var.port + location = var.location + resource_group_name = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name + container_app_environment_id = var.container_app_environment_id + log_analytics_workspace_id = var.log_analytics_workspace_id + image = var.image + min_replicas = var.min_replicas + max_replicas = var.max_replicas + user_assigned_identity_ids = concat(var.user_assigned_identity_ids, [module.port_ocean_authorization.user_assigned_identity_id]) + additional_secrets = var.additional_secrets +} \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/authorization/main.tf b/deployment/terraform/azure/containerapp/modules/authorization/main.tf new file mode 100644 index 0000000000..7b9c65a867 --- /dev/null +++ b/deployment/terraform/azure/containerapp/modules/authorization/main.tf @@ -0,0 +1,31 @@ +data "azurerm_subscription" "current_subscription" {} + +locals { + prefix = "port-ocean" + subscription_id = var.subscription_id != null ? var.subscription_id : data.azurerm_subscription.current_subscription.id +} + +resource "azurerm_role_definition" "ocean-azure-role-definition" { + name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-rd" + scope = local.subscription_id + description = "This role is used by ${var.integration.type}-${var.integration.identifier} Port Ocean integration" + permissions { + actions = var.permissions.actions + not_actions = var.permissions.not_actions + data_actions = var.permissions.data_actions + not_data_actions = var.permissions.not_data_actions + } + assignable_scopes = var.permissions_scope != null ? var.permissions_scope : [local.subscription_id] +} + +resource "azurerm_user_assigned_identity" "ocean-azure-assigned-identity" { + name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-identity" + location = var.location + resource_group_name = var.resource_group_name +} + +resource "azurerm_role_assignment" "ocean-azure-role-assignment" { + scope = data.azurerm_subscription.current_subscription.id + role_definition_id = azurerm_role_definition.ocean-azure-role-definition.role_definition_resource_id + principal_id = azurerm_user_assigned_identity.ocean-azure-assigned-identity.principal_id +} diff --git a/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf b/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf new file mode 100644 index 0000000000..5dd0d4aaee --- /dev/null +++ b/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf @@ -0,0 +1,3 @@ +output "user_assigned_identity_id" { + value = azurerm_user_assigned_identity.ocean-azure-assigned-identity.id +} \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/authorization/variables.tf b/deployment/terraform/azure/containerapp/modules/authorization/variables.tf new file mode 100644 index 0000000000..6b35d3c91d --- /dev/null +++ b/deployment/terraform/azure/containerapp/modules/authorization/variables.tf @@ -0,0 +1,41 @@ +variable "integration_version" { + type = string + default = "latest" +} + +variable "integration" { + type = object({ + identifier = optional(string) + type = string + config = map(any) + }) +} + +variable "location" { + type = string + default = "West US 2 " +} + +variable "subscription_id" { + type = string + default = null +} + +variable "resource_group_name" { + type = string + default = null +} + +variable "permissions" { + type = object({ + actions = optional(list(string)) + data_actions = optional(list(string)) + not_actions = optional(list(string)) + not_data_actions = optional(list(string)) + }) +} + +variable "permissions_scope" { + type = list(string) + default = null +} \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/container_app/main.tf b/deployment/terraform/azure/containerapp/modules/container_app/main.tf new file mode 100644 index 0000000000..e3562166af --- /dev/null +++ b/deployment/terraform/azure/containerapp/modules/container_app/main.tf @@ -0,0 +1,102 @@ +data "azurerm_subscription" "current_subscription" {} + +locals { + prefix = "port-ocean" + env = [ + { + name = upper("OCEAN__INITIALIZE_PORT_RESOURCES"), + value = var.initialize_port_resources ? "true" : "false" + }, + { + name = upper("OCEAN__EVENT_LISTENER") + value = jsonencode({ + for key, value in var.event_listener : key => value if value != null + }) + }, + { + name = upper("OCEAN__INTEGRATION") + value = jsonencode(var.integration) + } + ] + port_credentials_secret_name = "ocean-port-credentials" +} + +resource "azurerm_log_analytics_workspace" "ocean-log-analytics" { + count = var.log_analytics_workspace_id != null ? 0 : 1 + name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-law" + location = var.location + resource_group_name = var.resource_group_name + sku = "PerGB2018" + retention_in_days = 30 +} + +resource "azurerm_container_app_environment" "ocean-container-app-env" { + count = var.container_app_environment_id != null ? 0 : 1 + name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-env" + location = var.location + resource_group_name = var.resource_group_name + log_analytics_workspace_id = var.log_analytics_workspace_id != null ? var.log_analytics_workspace_id : azurerm_log_analytics_workspace.ocean-log-analytics[0].id +} + + +resource "azurerm_container_app" "ocean-container-app" { + name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}" + container_app_environment_id = var.container_app_environment_id != null ? var.container_app_environment_id : azurerm_container_app_environment.ocean-container-app-env[0].id + resource_group_name = var.resource_group_name + revision_mode = "Single" + identity { + type = "UserAssigned" + identity_ids = var.user_assigned_identity_ids + } + ingress { + external_enabled = var.assign_public_ip + target_port = var.container_port + traffic_weight { + percentage = 100 + latest_revision = true + } + } + template { + min_replicas = 1 + max_replicas = 1 + container { + name = "${local.prefix}-${var.integration.type}" + cpu = var.cpu + memory = var.memory + image = var.image != null ? var.image : "${var.image_registry}/port-ocean-${var.integration.type}:${var.integration_version}" + dynamic "env" { + for_each = local.env + content { + name = env.value.name + value = env.value.value + } + } + env { + name = "OCEAN__PORT" + secret_name = local.port_credentials_secret_name + } + dynamic "env" { + for_each = var.additional_secrets + content { + name = env.key + secret_name = replace("ocean-${lower(env.key)}", "_", "-") + } + } + } + } + secret { + name = local.port_credentials_secret_name + value = jsonencode({ + client_secret : var.port.client_secret, + client_id : var.port.client_id, + }) + } + dynamic "secret" { + for_each = var.additional_secrets + content { + name = replace("ocean-${lower(secret.key)}", "_", "-") + value = secret.value + } + } +} + diff --git a/deployment/terraform/azure/containerapp/modules/container_app/outputs.tf b/deployment/terraform/azure/containerapp/modules/container_app/outputs.tf new file mode 100644 index 0000000000..84935ac180 --- /dev/null +++ b/deployment/terraform/azure/containerapp/modules/container_app/outputs.tf @@ -0,0 +1,11 @@ +output "container_app_latest_fqdn" { + value = azurerm_container_app.ocean-container-app.latest_revision_fqdn +} + +output "container_app_outbound_ip_addresses" { + value = azurerm_container_app.ocean-container-app.outbound_ip_addresses +} + +output "container_latest_revision_name" { + value = azurerm_container_app.ocean-container-app.latest_revision_name +} \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf new file mode 100644 index 0000000000..d45a9151ec --- /dev/null +++ b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf @@ -0,0 +1,123 @@ +variable "event_listener" { + type = object({ + type = string + + # POLLING + resync_on_start = optional(bool) + interval = optional(number) + + # WEBHOOK + app_host = optional(string) + + + # KAFKA + brokers = optional(list(string)) + security_protocol = optional(list(string)) + authentication_mechanism = optional(list(string)) + kafka_security_enabled = optional(list(bool)) + consumer_poll_timeout = optional(list(number)) + }) + + default = { + type = "POLLING" + } +} + +variable "additional_secrets" { + type = map(string) + default = {} +} + +variable "port" { + type = object({ + client_id = string + client_secret = string + }) +} + +variable "initialize_port_resources" { + type = bool + default = false +} + +variable "integration_version" { + type = string + default = "latest" +} + +variable "integration" { + type = object({ + identifier = optional(string) + type = string + config = map(any) + }) +} + +variable "assign_public_ip" { + type = bool + default = true +} + +variable "container_port" { + default = 8000 +} + +variable "image_registry" { + type = string + default = "ghcr.io/port-labs" +} + +variable "location" { + type = string + default = "West US 2 " +} + +variable "subscription_id" { + type = string + default = null +} + +variable "resource_group_name" { + type = string + default = null +} + +variable "log_analytics_workspace_id" { + type = string + default = null +} + +variable "container_app_environment_id" { + type = string + default = null +} + +variable "cpu" { + type = string + default = "1.0" +} + +variable "memory" { + type = string + default = "2Gi" +} + +variable "image" { + type = string + default = null +} + +variable "min_replicas" { + type = number + default = 1 +} + +variable "max_replicas" { + type = number + default = 1 +} + +variable "user_assigned_identity_ids" { + type = list(string) + default = [] +} \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/providers.tf b/deployment/terraform/azure/containerapp/providers.tf new file mode 100644 index 0000000000..239b407c35 --- /dev/null +++ b/deployment/terraform/azure/containerapp/providers.tf @@ -0,0 +1,27 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "3.68.0" + } + env = { + source = "tchupp/env" + version = "0.0.2" + } + jsonschema = { + source = "bpedman/jsonschema" + version = "0.2.1" + } + } +} +provider "azurerm" { + # The AzureRM Provider supports authenticating using via the Azure CLI, a Managed Identity + # and a Service Principal. More information on the authentication methods supported by + # the AzureRM Provider can be found here: + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure + + # The features block allows changing the behaviour of the Azure Provider, more + # information can be found here: + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/features-block + features {} +} diff --git a/deployment/terraform/azure/containerapp/variables.tf b/deployment/terraform/azure/containerapp/variables.tf new file mode 100644 index 0000000000..a4abed0511 --- /dev/null +++ b/deployment/terraform/azure/containerapp/variables.tf @@ -0,0 +1,138 @@ +variable "event_listener" { + type = object({ + type = string + + # POLLING + resync_on_start = optional(bool) + interval = optional(number) + + # WEBHOOK + app_host = optional(string) + + + # KAFKA + brokers = optional(list(string)) + security_protocol = optional(list(string)) + authentication_mechanism = optional(list(string)) + kafka_security_enabled = optional(list(bool)) + consumer_poll_timeout = optional(list(number)) + }) + + default = { + type = "POLLING" + } +} + +variable "additional_secrets" { + type = map(string) + default = {} +} + +variable "port" { + type = object({ + client_id = string + client_secret = string + }) +} + +variable "initialize_port_resources" { + type = bool + default = false +} + +variable "integration_version" { + type = string + default = "latest" +} + +variable "integration" { + type = object({ + identifier = optional(string) + type = string + config = map(any) + }) +} + +variable "assign_public_ip" { + type = bool + default = true +} + +variable "container_port" { + default = 8000 +} + +variable "image_registry" { + type = string + default = "ghcr.io/port-labs" +} + + +variable "location" { + type = string + default = "West US 2 " +} + +variable "subscription_id" { + type = string + default = null +} + +variable "resource_group_name" { + type = string + default = null +} + +variable "log_analytics_workspace_id" { + type = string + default = null +} + +variable "container_app_environment_id" { + type = string + default = null +} + +variable "cpu" { + type = string + default = "1.0" +} + +variable "memory" { + type = string + default = "2Gi" +} + +variable "image" { + type = string + default = null +} + +variable "min_replicas" { + type = number + default = 1 +} + +variable "max_replicas" { + type = number + default = 1 +} + +variable "permissions" { + type = object({ + actions = optional(list(string)) + data_actions = optional(list(string)) + not_actions = optional(list(string)) + not_data_actions = optional(list(string)) + }) +} + +variable "permissions_scope" { + type = list(string) + default = null +} + +variable "user_assigned_identity_ids" { + type = list(string) + default = [] +} \ No newline at end of file From 4f46a8a1b31247f8a0cbc141eeb3c092b5230256 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Tue, 8 Aug 2023 16:31:33 +0300 Subject: [PATCH 34/95] bump port ocean requirement 0.1.3 --- integrations/azure/poetry.lock | 2 +- integrations/azure/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock index 1623dd33df..4c9f29f521 100644 --- a/integrations/azure/poetry.lock +++ b/integrations/azure/poetry.lock @@ -2039,4 +2039,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "448f9b57713f255eeff2bed21a3ad5251db87d928f4b573596ef4a4348d377f1" +content-hash = "1412e543a22721e792bfb8746caa8dc27b09de215e2f80b274c1bff41872b137" diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml index 9abfd737a2..751302e5d1 100644 --- a/integrations/azure/pyproject.toml +++ b/integrations/azure/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Tom Tankilevitch "] [tool.poetry.dependencies] python = "^3.11" -port_ocean = {version = ">=0.1.2,<0.2.0", extras = ["cli"]} +port_ocean = {version = "^0.1.3", extras = ["cli"]} # due to patching the azure-mgmt-resource package, we need to use a specific version azure-mgmt-resource = "23.0.1" azure-identity = "^1.13.0" From 393644731fa48c2cf6b1b5f92c461d9dbcb644fe Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Tue, 8 Aug 2023 16:31:54 +0300 Subject: [PATCH 35/95] bump rc8 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 2507cdd93f..8788b34beb 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc7 +version: v0.1.0rc8 type: azure description: azure integration for Port Ocean icon: Cookiecutter From f9dc6059e27d0557930d7d01cc52a66408bcd855 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Tue, 8 Aug 2023 17:27:29 +0300 Subject: [PATCH 36/95] minor fixes --- .../azure/containerapp/modules/container_app/main.tf | 3 +-- .../containerapp/modules/container_app/variables.tf | 1 + deployment/terraform/azure/containerapp/variables.tf | 1 + integrations/azure/config.yaml | 11 +---------- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/deployment/terraform/azure/containerapp/modules/container_app/main.tf b/deployment/terraform/azure/containerapp/modules/container_app/main.tf index e3562166af..8d9601f0e9 100644 --- a/deployment/terraform/azure/containerapp/modules/container_app/main.tf +++ b/deployment/terraform/azure/containerapp/modules/container_app/main.tf @@ -87,8 +87,7 @@ resource "azurerm_container_app" "ocean-container-app" { secret { name = local.port_credentials_secret_name value = jsonencode({ - client_secret : var.port.client_secret, - client_id : var.port.client_id, + for key, value in var.port : key => value if value != null }) } dynamic "secret" { diff --git a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf index d45a9151ec..dada76552b 100644 --- a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf +++ b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf @@ -32,6 +32,7 @@ variable "port" { type = object({ client_id = string client_secret = string + base_url = optional(string) }) } diff --git a/deployment/terraform/azure/containerapp/variables.tf b/deployment/terraform/azure/containerapp/variables.tf index a4abed0511..cbfd5f4da4 100644 --- a/deployment/terraform/azure/containerapp/variables.tf +++ b/deployment/terraform/azure/containerapp/variables.tf @@ -32,6 +32,7 @@ variable "port" { type = object({ client_id = string client_secret = string + base_url = optional(string) }) } diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml index 68cd0dbc19..fae42f4d43 100644 --- a/integrations/azure/config.yaml +++ b/integrations/azure/config.yaml @@ -5,18 +5,9 @@ port: clientId: "{{ from env PORT_CLIENT_ID }}" # Can be loaded via environment variable: PORT_CLIENT_ID, if both are set, the environment variable will be used. clientSecret: "{{ from env PORT_CLIENT_SECRET }}" # Can be loaded via environment variable: PORT_CLIENT_SECRET, if both are set, the environment variable will be used. baseUrl: "{{ from env PORT_BASE_URL }}" -# baseUrl: http://localhost:3000 # Can be loaded via environment variable: PORT_BASE_URL, if both are set, the environment variable will be used. # The event listener to use for the integration service. eventListener: -# type: SAMPLE - type: POLLING -# brokers: {{ from env EVENT_LISTENER_BROKERS }} -# username: {{ from env EVENT_LISTENER_USERNAME }} -# password: {{ from env EVENT_LISTENER_PASSWORD }} -# consumerGroup: {{ from env EVENT_LISTENER_CONSUMER_GROUP }} -# authenticationMechanism: {{ from env EVENT_LISTENER_AUTHENTICATION_MECHANISM }} -# kafkaSecurityEnabled: true -# brokers: "localhost:9092" + type: POLLING integration: # The identifier of this integration instance. identifier: "{{ from env INTEGRATION_IDENTIFIER }}" From 9df3f00e09dddbee03770d258fc04053b244dcf5 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 9 Aug 2023 21:42:32 +0300 Subject: [PATCH 37/95] update terraform modules --- .../terraform/azure/containerapp/main.tf | 2 ++ .../modules/authorization/outputs.tf | 8 ++++++ .../modules/container_app/main.tf | 13 ++++++++- .../modules/container_app/variables.tf | 10 +++++++ .../terraform/azure/containerapp/outputs.tf | 27 +++++++++++++++++++ .../terraform/azure/containerapp/variables.tf | 5 ++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 deployment/terraform/azure/containerapp/outputs.tf diff --git a/deployment/terraform/azure/containerapp/main.tf b/deployment/terraform/azure/containerapp/main.tf index f94d7f15a4..662281efb2 100644 --- a/deployment/terraform/azure/containerapp/main.tf +++ b/deployment/terraform/azure/containerapp/main.tf @@ -30,4 +30,6 @@ module "port_ocean_container_app" { max_replicas = var.max_replicas user_assigned_identity_ids = concat(var.user_assigned_identity_ids, [module.port_ocean_authorization.user_assigned_identity_id]) additional_secrets = var.additional_secrets + user_assigned_client_id = module.port_ocean_authorization.user_assigned_identity_client_id + additional_environment_variables = var.additional_environment_variables } \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf b/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf index 5dd0d4aaee..87442b7eab 100644 --- a/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf +++ b/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf @@ -1,3 +1,11 @@ output "user_assigned_identity_id" { value = azurerm_user_assigned_identity.ocean-azure-assigned-identity.id +} + +output "user_assigned_identity_client_id" { + value = azurerm_user_assigned_identity.ocean-azure-assigned-identity.client_id +} + +output "subscription_id" { + value = local.subscription_id } \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/container_app/main.tf b/deployment/terraform/azure/containerapp/modules/container_app/main.tf index 8d9601f0e9..240cb2aeab 100644 --- a/deployment/terraform/azure/containerapp/modules/container_app/main.tf +++ b/deployment/terraform/azure/containerapp/modules/container_app/main.tf @@ -71,6 +71,17 @@ resource "azurerm_container_app" "ocean-container-app" { value = env.value.value } } + dynamic "env" { + for_each = var.additional_environment_variables + content { + name = env.key + value = env.value + } + } + env { + name = "AZURE_CLIENT_ID" + value = var.user_assigned_client_id + } env { name = "OCEAN__PORT" secret_name = local.port_credentials_secret_name @@ -78,7 +89,7 @@ resource "azurerm_container_app" "ocean-container-app" { dynamic "env" { for_each = var.additional_secrets content { - name = env.key + name = env.key secret_name = replace("ocean-${lower(env.key)}", "_", "-") } } diff --git a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf index dada76552b..c5059c3be4 100644 --- a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf +++ b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf @@ -28,6 +28,11 @@ variable "additional_secrets" { default = {} } +variable "additional_environment_variables" { + type = map(string) + default = {} +} + variable "port" { type = object({ client_id = string @@ -121,4 +126,9 @@ variable "max_replicas" { variable "user_assigned_identity_ids" { type = list(string) default = [] +} + +variable "user_assigned_client_id" { + type = string + default = null } \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/outputs.tf b/deployment/terraform/azure/containerapp/outputs.tf new file mode 100644 index 0000000000..31402d1325 --- /dev/null +++ b/deployment/terraform/azure/containerapp/outputs.tf @@ -0,0 +1,27 @@ +output "container_app_latest_fqdn" { + value = module.port_ocean_container_app.container_app_latest_fqdn +} + +output "container_app_outbound_ip_addresses" { + value = module.port_ocean_container_app.container_app_outbound_ip_addresses +} + +output "container_latest_revision_name" { + value = module.port_ocean_container_app.container_latest_revision_name +} + +output "resource_group_name" { + value = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name +} + +output "location" { + value = var.location +} + +output "subscription_id" { + value = module.port_ocean_authorization.subscription_id +} + +output "integration" { + value = var.integration +} diff --git a/deployment/terraform/azure/containerapp/variables.tf b/deployment/terraform/azure/containerapp/variables.tf index cbfd5f4da4..087d839937 100644 --- a/deployment/terraform/azure/containerapp/variables.tf +++ b/deployment/terraform/azure/containerapp/variables.tf @@ -28,6 +28,11 @@ variable "additional_secrets" { default = {} } +variable "additional_environment_variables" { + type = map(string) + default = {} +} + variable "port" { type = object({ client_id = string From 3d38ecc8140e249aa8d31e9d5cc08df39c633861 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 9 Aug 2023 21:42:51 +0300 Subject: [PATCH 38/95] update blueprints and app config --- .../azure/.port/resources/blueprints.json | 207 +++++++++++++++++- .../.port/resources/port-app-config.yaml | 76 ++++++- 2 files changed, 280 insertions(+), 3 deletions(-) diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index a10a73a14d..1f16ee465e 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -1,8 +1,8 @@ [ { - "identifier": "containerapp", + "identifier": "container_app", "description": "This blueprint represents an Azure Container App in our software catalog", - "title": "Container APP", + "title": "Container App", "icon": "Azure", "schema": { "properties": { @@ -36,5 +36,208 @@ } } } + }, + { + "identifier": "aks", + "description": "This blueprint represents an Azure Kubernetes Service in our software catalog", + "title": "AKS", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioning_state": { + "title": "Provisioning State", + "type": "string" + }, + "power_state": { + "title": "Power State", + "type": "string" + }, + "kubernetes_version": { + "title": "Kubernetes Version", + "type": "string" + }, + "current_kubernetes_version": { + "title": "Current Kubernetes Version", + "type": "string" + }, + "dns_prefix": { + "title": "DNS Prefix", + "type": "string" + }, + "fqdn": { + "title": "FQDN", + "type": "string" + }, + "node_resource_group": { + "title": "Node Resource Group", + "type": "string" + }, + "enable_rbac": { + "title": "Enable RBAC", + "type": "boolean" + }, + "support_plan": { + "title": "Support Plan", + "type": "string" + }, + "network_plugin": { + "title": "Network Plugin", + "type": "string" + }, + "pod_cidr": { + "title": "Pod CIDR", + "type": "string" + }, + "service_cidr": { + "title": "Service CIDR", + "type": "string" + }, + "dns_service_ip": { + "title": "DNS Service IP", + "type": "string" + }, + "outbound_type": { + "title": "Outbound Type", + "type": "string" + }, + "load_balancer_sku": { + "title": "Load Balancer SKU", + "type": "string" + }, + "max_agent_pools": { + "title": "Max Agent Pools", + "type": "number" + }, + "sku_tier": { + "title": "Tier", + "type": "string" + } + } + } + }, + { + "identifier": "Load_balancer", + "description": "This blueprint represents an Azure Load Balancer in our software catalog", + "title": "Load Balancer", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "tags": { + "title": "Tags", + "type": "object" + }, + "provisioning_state": { + "title": "Provisioning State", + "type": "string" + }, + "frontend_ip_resource_ids": { + "title": "Frontend IP Resource IDs", + "type": "array" + }, + "backend_address_pools_resource_ids": { + "title": "Backend Address Pools Resource IDs", + "type": "array" + }, + "load_balancing_rules_resource_ids": { + "title": "Load Balancing Rules Resource IDs", + "type": "array" + }, + "probes_resource_ids": { + "title": "Probes Resource IDs", + "type": "array" + }, + "inbound_nat_rules_resource_ids": { + "title": "Inbound NAT Rules Resource IDs", + "type": "array" + }, + "inbound_nat_pools_resource_ids": { + "title": "Inbound NAT Pools Resource IDs", + "type": "array" + } + } + } + }, + { + "identifier": "storage_account", + "description": "This blueprint represents an Azure Storage Account in our software catalog", + "title": "Storage Account", + "icon": "Azure", + "schema" : { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioning_state": { + "title": "Provisioning State", + "type": "string" + }, + "creation_time": { + "title": "Creation Time", + "type": "string" + }, + "is_hns_enabled": { + "title": "Is HNS Enabled", + "type": "boolean", + "default": false + }, + "file_encryption_enabled": { + "title": "File Encryption Enabled", + "type": "boolean" + }, + "blob_encryption_enabled": { + "title": "Blob Encryption Enabled", + "type": "boolean" + }, + "primary_location": { + "title": "Primary Location", + "type": "string" + }, + "secondary_location": { + "title": "Secondary Location", + "type": "string" + }, + "status_of_primary": { + "title": "Status of Primary", + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "enumColors": { + "unavailable": "red", + "available": "green" + }, + }, + "status_of_secondary": { + "title": "Status of Secondary", + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "enumColors": { + "unavailable": "red", + "available": "green" + } + }, + "tags": { + "title": "Tags", + "type": "object" + }, + "allow_blob_public_access": { + "title": "Allow Blob Public Access", + "type": "boolean" + } + } + } } ] \ No newline at end of file diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index abb8eed8a3..2968496f78 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -9,7 +9,7 @@ resources: mappings: identifier: .id title: .name - blueprint: '"containerapp"' + blueprint: '"container_app"' properties: location: .location provisioning_state: .properties.provisioningState @@ -18,3 +18,77 @@ resources: host_name: .properties.configuration.ingress.fqdn min_replicas: .properties.template.scale.minReplicas max_replicas: .properties.template.scale.maxReplicas + - kind: Microsoft.ContainerService/managedClusters + selector: + query: "true" + # azure resource api version to query + api_version: '2023-05-01' + port: + entity: + mappings: + identifier: .id + title: .name + blueprint: '"aks"' + properties: + location: .location + provisioning_state: .properties.provisioningState + power_state: .properties.powerState.code + kubernetes_version: .properties.kubernetesVersion + current_kubernetes_version: .properties.currentKubernetesVersion + dns_prefix: .properties.dnsPrefix + fqdn: .properties.fqdn + node_resource_group: .properties.nodeResourceGroup + enable_rbac: .properties.enableRBAC + support_plan: .properties.supportPlan + network_plugin: .properties.networkProfile.networkPlugin + pod_cidr: .properties.networkProfile.podCidr + service_cidr: .properties.networkProfile.serviceCidr + dns_service_ip: .properties.networkProfile.dnsServiceIP + outbound_type: .properties.networkProfile.outboundType + load_balancer_sku: .properties.networkProfile.loadBalancerSku + max_agent_pools: .properties.maxAgentPools + sku_tier: .properties.sku.tier + - kind: Microsoft.Network/loadBalancers + selector: + query: "true" + # azure resource api version to query + api_version: '2023-02-01' + port: + entity: + mappings: + identifier: .id + title: .name + blueprint: '"load_balancer"' + location: .location + provisioning_state: .properties.provisioningState + tags: .tags + frontend_ip_resource_ids: .properties.frontendIPConfigurations[].id + backend_address_pool_resource_ids: .properties.backendAddressPools[].id + load_balancing_rules_resource_ids: .properties.loadBalancingRules[].id + probes_resource_ids: .properties.probes[].id + inbound_nat_rules_resource_ids: .properties.inboundNatRules[].id + inbound_nat_pools_resource_ids: .properties.inboundNatPools[].id + - kind: Microsoft.Storage/storageAccounts + selector: + query: "true" + # azure resource api version to query + api_version: '2023-01-01' + port: + entity: + mappings: + identifier: .id + title: .name + blueprint: '"storage_account"' + location: .location + provisioning_state: .properties.provisioningState + creation_time: .properties.creationTime + is_hns_enabled: .properties.isHnsEnabled + file_encryption_enabled: .properties.encryption.services.file.enabled + blob_encryption_enabled: .properties.encryption.services.blob.enabled + primary_location: .properties.primaryLocation + secondary_location: .properties.secondaryLocation + status_of_primary: .properties.statusOfPrimary + status_of_secondary: .properties.statusOfSecondary + allow_blob_public_access: .properties.allowBlobPublicAccess + tags: .tags + From c0ac84793a3f560f3b5b6784642e1d2580f47014 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 9 Aug 2023 21:43:26 +0300 Subject: [PATCH 39/95] bump rc9 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 8788b34beb..8b1d2681fa 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc8 +version: v0.1.0rc9 type: azure description: azure integration for Port Ocean icon: Cookiecutter From 02e6c0cf5a3effad8a3b6fe872a68515ad8ac51b Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 9 Aug 2023 22:24:35 +0300 Subject: [PATCH 40/95] fix --- deployment/terraform/azure/containerapp/main.tf | 1 + integrations/azure/.port/resources/blueprints.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deployment/terraform/azure/containerapp/main.tf b/deployment/terraform/azure/containerapp/main.tf index 662281efb2..2d8b2ee910 100644 --- a/deployment/terraform/azure/containerapp/main.tf +++ b/deployment/terraform/azure/containerapp/main.tf @@ -21,6 +21,7 @@ module "port_ocean_container_app" { source= "./modules/container_app" integration = var.integration port = var.port + initialize_port_resources = var.initialize_port_resources location = var.location resource_group_name = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name container_app_environment_id = var.container_app_environment_id diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index 1f16ee465e..24b63fcd14 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -120,7 +120,7 @@ } }, { - "identifier": "Load_balancer", + "identifier": "load_balancer", "description": "This blueprint represents an Azure Load Balancer in our software catalog", "title": "Load Balancer", "icon": "Azure", @@ -215,7 +215,7 @@ "enumColors": { "unavailable": "red", "available": "green" - }, + } }, "status_of_secondary": { "title": "Status of Secondary", From 53c47a150631e33e6e47097646328eee1d29b925 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Wed, 9 Aug 2023 22:27:46 +0300 Subject: [PATCH 41/95] bump rc10 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 8b1d2681fa..4b3f8fcddf 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc9 +version: v0.1.0rc10 type: azure description: azure integration for Port Ocean icon: Cookiecutter From bbce6b740c079875d92f0695de005f7fbab4bf14 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 00:30:43 +0300 Subject: [PATCH 42/95] align blueprints and mapping --- .../azure/.port/resources/blueprints.json | 62 ++++++++++++++ .../.port/resources/port-app-config.yaml | 85 +++++++++++++------ 2 files changed, 122 insertions(+), 25 deletions(-) diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index 24b63fcd14..bbf293b33c 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -1,4 +1,26 @@ [ + { + "identifier": "resource_group", + "description": "This blueprint represents an Azure Resource Group in our software catalog", + "title": "Resource Group", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioning_state": { + "title": "Provisioning State", + "type": "string" + }, + "tags": { + "title": "Tags", + "type": "object" + } + } + } + }, { "identifier": "container_app", "description": "This blueprint represents an Azure Container App in our software catalog", @@ -34,6 +56,16 @@ "title": "Max Replicas", "type": "number" } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resource_group": { + "target": "resource_group", + "required": true, + "many": false } } }, @@ -116,6 +148,16 @@ "title": "Tier", "type": "string" } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resource_group": { + "target": "resource_group", + "required": true, + "many": false } } }, @@ -162,6 +204,16 @@ "title": "Inbound NAT Pools Resource IDs", "type": "array" } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resource_group": { + "target": "resource_group", + "required": true, + "many": false } } }, @@ -237,6 +289,16 @@ "title": "Allow Blob Public Access", "type": "boolean" } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resource_group": { + "target": "resource_group", + "required": true, + "many": false } } } diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index 2968496f78..0d0babbb78 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -1,4 +1,20 @@ resources: + - kind: Microsoft.Resources/resourceGroups + selector: + query: "true" + # azure resource api version to query + api_version: '2022-09-01' + port: + entity: + mappings: + # lowercase only the resourceGroups namespace so other azure resources will be able to relate to it + identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + title: .name + blueprint: '"resource_group"' + properties: + location: .location + provisioning_state: .properties.provisioning_state + tags: .tags - kind: Microsoft.App/containerApps selector: query: "true" @@ -7,7 +23,8 @@ resources: port: entity: mappings: - identifier: .id + # lowercase only the resourceGroups namespace to adjust with other azure api resources + identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' title: .name blueprint: '"container_app"' properties: @@ -18,6 +35,8 @@ resources: host_name: .properties.configuration.ingress.fqdn min_replicas: .properties.template.scale.minReplicas max_replicas: .properties.template.scale.maxReplicas + relations: + resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.ContainerService/managedClusters selector: query: "true" @@ -26,7 +45,8 @@ resources: port: entity: mappings: - identifier: .id + # lowercase only the resourceGroups namespace to adjust with other azure api resources + identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' title: .name blueprint: '"aks"' properties: @@ -48,6 +68,11 @@ resources: load_balancer_sku: .properties.networkProfile.loadBalancerSku max_agent_pools: .properties.maxAgentPools sku_tier: .properties.sku.tier + relations: + # resolve resource group id from aks id + # AKS id contains the resourcegroups namespace instead of resourceGroups therefore we need to lowercase + # all other resourceGroups namespaces of other azure resources so we will be able to relate them to resource group blueprint + resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Network/loadBalancers selector: query: "true" @@ -56,18 +81,23 @@ resources: port: entity: mappings: - identifier: .id + # lowercase only the resourceGroups namespace to adjust with other azure api resources + identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' title: .name blueprint: '"load_balancer"' - location: .location - provisioning_state: .properties.provisioningState - tags: .tags - frontend_ip_resource_ids: .properties.frontendIPConfigurations[].id - backend_address_pool_resource_ids: .properties.backendAddressPools[].id - load_balancing_rules_resource_ids: .properties.loadBalancingRules[].id - probes_resource_ids: .properties.probes[].id - inbound_nat_rules_resource_ids: .properties.inboundNatRules[].id - inbound_nat_pools_resource_ids: .properties.inboundNatPools[].id + properties: + location: .location + provisioning_state: .properties.provisioningState + tags: .tags + frontend_ip_resource_ids: .properties.frontendIPConfigurations[].id + backend_address_pool_resource_ids: .properties.backendAddressPools[].id + load_balancing_rules_resource_ids: .properties.loadBalancingRules[].id + probes_resource_ids: .properties.probes[].id + inbound_nat_rules_resource_ids: .properties.inboundNatRules[].id + inbound_nat_pools_resource_ids: .properties.inboundNatPools[].id + relations: + # resolve resource group id from load balancer id + resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Storage/storageAccounts selector: query: "true" @@ -76,19 +106,24 @@ resources: port: entity: mappings: - identifier: .id + # lowercase only the resourceGroups namespace to adjust with other azure api resources + identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' title: .name blueprint: '"storage_account"' - location: .location - provisioning_state: .properties.provisioningState - creation_time: .properties.creationTime - is_hns_enabled: .properties.isHnsEnabled - file_encryption_enabled: .properties.encryption.services.file.enabled - blob_encryption_enabled: .properties.encryption.services.blob.enabled - primary_location: .properties.primaryLocation - secondary_location: .properties.secondaryLocation - status_of_primary: .properties.statusOfPrimary - status_of_secondary: .properties.statusOfSecondary - allow_blob_public_access: .properties.allowBlobPublicAccess - tags: .tags + properties: + location: .location + provisioning_state: .properties.provisioningState + creation_time: .properties.creationTime + is_hns_enabled: .properties.isHnsEnabled + file_encryption_enabled: .properties.encryption.services.file.enabled + blob_encryption_enabled: .properties.encryption.services.blob.enabled + primary_location: .properties.primaryLocation + secondary_location: .properties.secondaryLocation + status_of_primary: .properties.statusOfPrimary + status_of_secondary: .properties.statusOfSecondary + allow_blob_public_access: .properties.allowBlobPublicAccess + tags: .tags + relations: + # resolve resource group id from storage account id + resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' From 8348bdad5bfd5e5f4f754c0f6f01ea3065e3063f Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 00:31:28 +0300 Subject: [PATCH 43/95] add support for resource groups --- integrations/azure/azure_integration/ocean.py | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index 4869583436..2ddd416a67 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -6,6 +6,7 @@ from azure.core.exceptions import ResourceNotFoundError from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient + from azure_integration.utils import ( get_integration_subscription_id, get_port_resource_configuration_by_kind, @@ -14,9 +15,16 @@ from azure_integration.azure_patch import list_resources +RESOURCE_KINDS_WITH_SPECIAL_HANDLING = [ + "Microsoft.Resources/resourceGroups", +] + + @ocean.on_resync() -async def on_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - logger.debug("Resyncing", kind=kind) +async def resync_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: + if kind in RESOURCE_KINDS_WITH_SPECIAL_HANDLING: + logger.debug("Skipping resync", kind=kind) + return resource_config = await get_port_resource_configuration_by_kind(kind) api_version = resource_config["selector"]["api_version"] @@ -29,7 +37,27 @@ async def on_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: yield resource.as_dict() -@ocean.router.post("/azure/events") +@ocean.on_resync(kind="Microsoft.Resources/resourceGroups") +async def resync_resource_groups(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: + resource_config = await get_port_resource_configuration_by_kind(kind) + api_version = resource_config["selector"]["api_version"] + async with DefaultAzureCredential() as credential: + async with ResourceManagementClient( + credential=credential, subscription_id=get_integration_subscription_id() + ) as client: + async for resource_group in client.resource_groups.list( + api_version=api_version + ): + logger.debug( + "Found resource group", + resource_group_id=resource_group.id, + kind=kind, + api_version=api_version, + ) + yield resource_group.as_dict() + + +@ocean.router.post("/events") async def handle_events(request: Request): """ Handles System events from Azure Event Grid by the Azure subscription resource and registers them in Port From adbb6d0897751e91bf43277406165573430f896d Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 00:31:35 +0300 Subject: [PATCH 44/95] lint --- integrations/azure/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/azure/main.py b/integrations/azure/main.py index 353921188b..f1db166cae 100644 --- a/integrations/azure/main.py +++ b/integrations/azure/main.py @@ -3,9 +3,9 @@ from azure_integration import ocean from integration import cloud_event_validation_middleware_handler -from port_ocean.context.ocean import ocean +from port_ocean.context.ocean import ocean as port_ocean -ocean.app.fast_api_app.middleware("azure_cloud_event")( +port_ocean.app.fast_api_app.middleware("azure_cloud_event")( cloud_event_validation_middleware_handler ) From 2bbf513a1fa1db6bbab4d75680cb1eef708ae386 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 00:32:23 +0300 Subject: [PATCH 45/95] bump rc11 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 4b3f8fcddf..0d9ee16835 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc10 +version: v0.1.0rc11 type: azure description: azure integration for Port Ocean icon: Cookiecutter From 59e216f6e2b32d612a0e774e9947ec7d607b7e2b Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 10:43:28 +0300 Subject: [PATCH 46/95] add virtual machine support --- .../azure/.port/resources/blueprints.json | 80 +++++++++++++++++++ .../.port/resources/port-app-config.yaml | 32 +++++++- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index bbf293b33c..d6b1c6683e 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -301,5 +301,85 @@ "many": false } } + }, + { + "identifier": "virtual_machine", + "description": "This blueprint represents an Azure Virtual Machine in our software catalog", + "title": "Virtual Machine", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioning_state": { + "title": "Provisioning State", + "type": "string" + }, + "vm_size": { + "title": "VM Size", + "type": "string" + }, + "os_disk_name": { + "title": "OS Disk Name", + "type": "string" + }, + "os_disk_type": { + "title": "OS Disk Type", + "type": "string" + }, + "os_disk_caching": { + "title": "OS Disk Caching", + "type": "string" + }, + "os_disk_size_gb": { + "title": "OS Disk Size GB", + "type": "number" + }, + "os_disk_create_option": { + "title": "OS Disk Create Option", + "type": "string" + }, + "network_interface_ids": { + "title": "Network Interface IDs", + "type": "array" + }, + "license_type": { + "title": "License Type", + "type": "string" + }, + "vm_os_profile": { + "title": "VM OS Profile", + "type": "object" + }, + "vm_hardware_profile": { + "title": "VM Hardware Profile", + "type": "object" + }, + "vm_storage_profile": { + "title": "VM Storage Profile", + "type": "object" + }, + "zones": { + "title": "Zones", + "type": "array" + }, + "tags": { + "title": "Tags", + "type": "object" + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resource_group": { + "target": "resource_group", + "required": true, + "many": false + } + } } ] \ No newline at end of file diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index 0d0babbb78..eddac38d4b 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -126,4 +126,34 @@ resources: relations: # resolve resource group id from storage account id resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' - + - kind: Microsoft.Network/virtualNetworks + selector: + query: "true" + # azure resource api version to query + api_version: '2023-02-01' + port: + entity: + mappings: + # lowercase only the resourceGroups namespace to adjust with other azure api resources + identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + title: .name + blueprint: '"virtual_network"' + properties: + location: .location + provisioning_state: .properties.provisioningState + vm_size: .properties.hardwareProfile.vmSize + os_disk_name: .properties.storageProfile.osDisk.name + os_type: .properties.storageProfile.osDisk.osType + os_disk_caching: .properties.storageProfile.osDisk.caching + os_disk_size_gb: .properties.storageProfile.osDisk.diskSizeGB + os_disk_create_option: .properties.storageProfile.osDisk.createOption + network_interface_ids: .properties.networkProfile.networkInterfaces[].id + license_type: .properties.licenseType + vm_os_profile: .properties.osProfile + vm_hardware_profile: .properties.hardwareProfile + vm_storage_profile: .properties.storageProfile + zones: .properties.zones + tags: .tags + relations: + # resolve resource group id from virtual network id + resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' \ No newline at end of file From 4de6ecb57a784dac1c2baeb10f12ba8e49264517 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 10:44:07 +0300 Subject: [PATCH 47/95] add comment --- integrations/azure/azure_integration/azure_patch.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integrations/azure/azure_integration/azure_patch.py b/integrations/azure/azure_integration/azure_patch.py index 8503135fbf..9634c88731 100644 --- a/integrations/azure/azure_integration/azure_patch.py +++ b/integrations/azure/azure_integration/azure_patch.py @@ -80,6 +80,9 @@ async def list_resources( yield resource +# Patch the build_resources_list_request method in the resources operations module +# This is done to be able to query the resource provider instead of the resources api +# The original method is being called inside the list method in the resources client old_build_resources_list_request = ( azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request ) From 2e514bb2c0744f51635e78bee67078bd9cb27756 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 10:44:58 +0300 Subject: [PATCH 48/95] add main example --- .../examples/azure-integration/main.tf | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 deployment/terraform/azure/containerapp/examples/azure-integration/main.tf diff --git a/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf b/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf new file mode 100644 index 0000000000..4cf3d19c69 --- /dev/null +++ b/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf @@ -0,0 +1,97 @@ +module "ocean_integration" { + source = "../.." + + # required port parameters so that the integration could communicate with Port + port = { + client_id = "xxxxxxx" + client_secret = "xxxxxxx" + } + + initialize_port_resources = true + + # required port integration parameters so Port could identify the integration + integration = { + type = "azure" + identifier = "az1" + config = { + } + } + # optional port integration parameters + subscription_id = "/subscriptions/xxxxxx" + location = "East US 2" +# resource_group_name = "DefaultResourceGroup-EUS" +# container_app_environment_id = "/subscriptions/7026e601-6a3b-4210-a9d6-b8742aa3b9f9/resourceGroups/DefaultResourceGroup-EUS/providers/Microsoft.App/managedEnvironments/managedEnvironment-DefaultResource-9253" +# log_analytics_workspace_id = "b049b4f3-a4cb-4229-a5b0-36fbc6f34361" + + image = "ghcr.io/port-labs/port-ocean-azure:v0.1.0rc11" + + permissions = { + actions = [ + "microsoft.app/containerapps/read", + "Microsoft.Storage/storageAccounts/read", + "Microsoft.ContainerService/managedClusters/read", + "Microsoft.Network/loadBalancers/read", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Resources/subscriptions/resources/read", + ] + not_actions = [] + data_actions = [] + not_data_actions = [] + } + + additional_secrets = { + OCEAN__INTEGRATION__CONFIG__SUBSCRIPTION_ID = "xxxxxxxxx" + } + additional_environment_variables = { + OCEAN__INTEGRATION__CONFIG__SOME_ENV_VAR = "some-value" + } +} + +resource "azurerm_eventgrid_system_topic" "subscription_event_grid_topic" { + name = "subscription-event-grid-topic" + resource_group_name = module.ocean_integration.resource_group_name + location = "Global" + topic_type = "Microsoft.Resources.Subscriptions" + source_arm_resource_id = module.ocean_integration.subscription_id +} + + +resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event_grid_topic_subscription" { + name = replace(replace("ocean-${module.ocean_integration.integration.type}-${module.ocean_integration.integration.identifier}-subscription","_", "-"),".","-") + resource_group_name = azurerm_eventgrid_system_topic.subscription_event_grid_topic.resource_group_name + system_topic = azurerm_eventgrid_system_topic.subscription_event_grid_topic.name + + included_event_types = [ + "Microsoft.Resources.ResourceWriteSuccess", + "Microsoft.Resources.ResourceWriteFailure", + "Microsoft.Resources.ResourceDeleteSuccess", + "Microsoft.Resources.ResourceDeleteFailure", + ] + event_delivery_schema = "CloudEventSchemaV1_0" + webhook_endpoint { + url = "https://${module.ocean_integration.container_app_latest_fqdn}/integration/azure/events" + } + advanced_filtering_on_arrays_enabled = true + advanced_filter { + string_contains { + key = "data.operationName" + values = [ + "microsoft.app/containerapps", + "Microsoft.Storage/storageAccounts", + "Microsoft.ContainerService/managedClusters", + "Microsoft.Network/loadBalancers", + "Microsoft.Resources/subscriptions/resourceGroups", + ] + } + } + delivery_property { + header_name = "Access-Control-Request-Method" + type = "Static" + value = "POST" + } + delivery_property { + header_name = "Origin" + type = "Static" + value = "azure" + } +} \ No newline at end of file From 490dbbaeec844b45fe07fc28b736601ae9c77774 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 10:46:40 +0300 Subject: [PATCH 49/95] remove comment --- .../azure/containerapp/examples/azure-integration/main.tf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf b/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf index 4cf3d19c69..7bf8406d4d 100644 --- a/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf +++ b/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf @@ -19,9 +19,7 @@ module "ocean_integration" { # optional port integration parameters subscription_id = "/subscriptions/xxxxxx" location = "East US 2" -# resource_group_name = "DefaultResourceGroup-EUS" -# container_app_environment_id = "/subscriptions/7026e601-6a3b-4210-a9d6-b8742aa3b9f9/resourceGroups/DefaultResourceGroup-EUS/providers/Microsoft.App/managedEnvironments/managedEnvironment-DefaultResource-9253" -# log_analytics_workspace_id = "b049b4f3-a4cb-4229-a5b0-36fbc6f34361" + image = "ghcr.io/port-labs/port-ocean-azure:v0.1.0rc11" From c4879e511ce8238f5b2210bcf3ff5032726c6f4f Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 11:06:02 +0300 Subject: [PATCH 50/95] add providers.tf to example --- .../examples/azure-integration/providers.tf | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 deployment/terraform/azure/containerapp/examples/azure-integration/providers.tf diff --git a/deployment/terraform/azure/containerapp/examples/azure-integration/providers.tf b/deployment/terraform/azure/containerapp/examples/azure-integration/providers.tf new file mode 100644 index 0000000000..260f326f74 --- /dev/null +++ b/deployment/terraform/azure/containerapp/examples/azure-integration/providers.tf @@ -0,0 +1,19 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "3.68.0" + } + } +} +provider "azurerm" { + # The AzureRM Provider supports authenticating using via the Azure CLI, a Managed Identity + # and a Service Principal. More information on the authentication methods supported by + # the AzureRM Provider can be found here: + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure + + # The features block allows changing the behaviour of the Azure Provider, more + # information can be found here: + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/features-block + features {} +} From 621aeadc3deddeddb5aafaa4e35f7826bfb179c1 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 11:16:31 +0300 Subject: [PATCH 51/95] oops adjust to virtualMachines --- integrations/azure/.port/resources/port-app-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index eddac38d4b..6345541286 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -126,7 +126,7 @@ resources: relations: # resolve resource group id from storage account id resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' - - kind: Microsoft.Network/virtualNetworks + - kind: Microsoft.Compute/virtualMachines selector: query: "true" # azure resource api version to query @@ -137,7 +137,7 @@ resources: # lowercase only the resourceGroups namespace to adjust with other azure api resources identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' title: .name - blueprint: '"virtual_network"' + blueprint: '"virtual_machine"' properties: location: .location provisioning_state: .properties.provisioningState @@ -155,5 +155,5 @@ resources: zones: .properties.zones tags: .tags relations: - # resolve resource group id from virtual network id + # resolve resource group id from virtual machine id resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' \ No newline at end of file From 0aa49929fe7f975278715662d03832c7e86ce87e Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 12:29:20 +0300 Subject: [PATCH 52/95] add description to azure modules variables --- .../terraform/azure/containerapp/main.tf | 4 +- .../modules/authorization/variables.tf | 9 ++++- .../modules/container_app/main.tf | 2 - .../modules/container_app/variables.tf | 36 ++++++++++++----- .../terraform/azure/containerapp/variables.tf | 40 +++++++++++++------ 5 files changed, 64 insertions(+), 27 deletions(-) diff --git a/deployment/terraform/azure/containerapp/main.tf b/deployment/terraform/azure/containerapp/main.tf index 2d8b2ee910..8a77721e82 100644 --- a/deployment/terraform/azure/containerapp/main.tf +++ b/deployment/terraform/azure/containerapp/main.tf @@ -29,8 +29,8 @@ module "port_ocean_container_app" { image = var.image min_replicas = var.min_replicas max_replicas = var.max_replicas - user_assigned_identity_ids = concat(var.user_assigned_identity_ids, [module.port_ocean_authorization.user_assigned_identity_id]) - additional_secrets = var.additional_secrets + user_assigned_identity_ids = module.port_ocean_authorization.user_assigned_identity_id user_assigned_client_id = module.port_ocean_authorization.user_assigned_identity_client_id + additional_secrets = var.additional_secrets additional_environment_variables = var.additional_environment_variables } \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/authorization/variables.tf b/deployment/terraform/azure/containerapp/modules/authorization/variables.tf index 6b35d3c91d..be3cc0f10d 100644 --- a/deployment/terraform/azure/containerapp/modules/authorization/variables.tf +++ b/deployment/terraform/azure/containerapp/modules/authorization/variables.tf @@ -1,6 +1,7 @@ variable "integration_version" { type = string default = "latest" + description = "The version of the integration to use" } variable "integration" { @@ -9,21 +10,25 @@ variable "integration" { type = string config = map(any) }) + description = "The integration to use" } variable "location" { type = string - default = "West US 2 " + default = "West US 2" + description = "The location to create the user assigned identity in" } variable "subscription_id" { type = string default = null + description = "The scope of the user assigned identity and the scope of the role definition" } variable "resource_group_name" { type = string default = null + description = "The resource group name to associate the user assigned identity with" } variable "permissions" { @@ -33,9 +38,11 @@ variable "permissions" { not_actions = optional(list(string)) not_data_actions = optional(list(string)) }) + description = "The permissions to grant to the user assigned identity" } variable "permissions_scope" { type = list(string) default = null + description = "The scope of the permissions" } \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/modules/container_app/main.tf b/deployment/terraform/azure/containerapp/modules/container_app/main.tf index 240cb2aeab..ad20443a71 100644 --- a/deployment/terraform/azure/containerapp/modules/container_app/main.tf +++ b/deployment/terraform/azure/containerapp/modules/container_app/main.tf @@ -1,5 +1,3 @@ -data "azurerm_subscription" "current_subscription" {} - locals { prefix = "port-ocean" env = [ diff --git a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf index c5059c3be4..6506822c5b 100644 --- a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf +++ b/deployment/terraform/azure/containerapp/modules/container_app/variables.tf @@ -21,16 +21,19 @@ variable "event_listener" { default = { type = "POLLING" } + description = "The event listener configuration" } variable "additional_secrets" { type = map(string) default = {} + description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name" } variable "additional_environment_variables" { type = map(string) default = {} + description = "Additional environment variables to be injected into the container" } variable "port" { @@ -39,16 +42,19 @@ variable "port" { client_secret = string base_url = optional(string) }) + description = "The port configuration, this is used to authenticate with the port API" } variable "initialize_port_resources" { type = bool default = false + description = "If true, the module will create the port resources required for the integration" } variable "integration_version" { type = string default = "latest" + description = "The version of the integration to deploy" } variable "integration" { @@ -57,78 +63,88 @@ variable "integration" { type = string config = map(any) }) + description = "The integration configuration, this is used to configure the integration and register it with port" } variable "assign_public_ip" { type = bool default = true + description = "If true, the container will be assigned a public IP" } variable "container_port" { default = 8000 + description = "The port the container will listen on" } variable "image_registry" { type = string default = "ghcr.io/port-labs" + description = "The image registry to pull the image from" } variable "location" { type = string - default = "West US 2 " -} - -variable "subscription_id" { - type = string - default = null + default = "West US 2" + description = "The location to deploy the container to" } variable "resource_group_name" { type = string default = null + description = "The name of the resource group to deploy the container to" } -variable "log_analytics_workspace_id" { +variable "container_app_environment_id" { type = string default = null + description = "The ID of the container app environment to deploy the container to, if not provided, a new container app environment will be created" } -variable "container_app_environment_id" { +variable "log_analytics_workspace_id" { type = string default = null + description = "The ID of the log analytics workspace to send logs to, if not provided, log analytics will be created" } variable "cpu" { type = string default = "1.0" + description = "The CPU to allocate to the container" } variable "memory" { type = string default = "2Gi" + description = "The memory to allocate to the container" } variable "image" { type = string default = null + description = "The image to deploy, if not provided, the latest image of the integration will be deployed" } variable "min_replicas" { type = number default = 1 + description = "The minimum number of replicas to deploy" } variable "max_replicas" { - type = number - default = 1 + type = number + default = 1 + description = "The maximum number of replicas to deploy" } variable "user_assigned_identity_ids" { type = list(string) default = [] + description = "The IDs of the user assigned identities to assign to the container" } variable "user_assigned_client_id" { type = string default = null + description = "The client ID of the user assigned identity to assign to the container" } \ No newline at end of file diff --git a/deployment/terraform/azure/containerapp/variables.tf b/deployment/terraform/azure/containerapp/variables.tf index 087d839937..906a625a34 100644 --- a/deployment/terraform/azure/containerapp/variables.tf +++ b/deployment/terraform/azure/containerapp/variables.tf @@ -21,16 +21,19 @@ variable "event_listener" { default = { type = "POLLING" } + description = "The event listener configuration" } variable "additional_secrets" { type = map(string) default = {} + description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name" } variable "additional_environment_variables" { type = map(string) default = {} + description = "Additional environment variables to be injected into the container" } variable "port" { @@ -39,16 +42,19 @@ variable "port" { client_secret = string base_url = optional(string) }) + description = "The port configuration, this is used to authenticate with the port API" } variable "initialize_port_resources" { type = bool default = false + description = "If true, the module will create the port resources required for the integration" } variable "integration_version" { type = string default = "latest" + description = "The version of the integration to deploy" } variable "integration" { @@ -57,71 +63,84 @@ variable "integration" { type = string config = map(any) }) + description = "The integration configuration, this is used to configure the integration and register it with port" } variable "assign_public_ip" { type = bool default = true + description = "If true, the container will be assigned a public IP" } variable "container_port" { default = 8000 + description = "The port the container will listen on" } variable "image_registry" { type = string default = "ghcr.io/port-labs" + description = "The image registry to pull the image from" } - variable "location" { type = string - default = "West US 2 " + default = "West US 2" + description = "The location to deploy the container to" } variable "subscription_id" { type = string default = null + description = "The scope of the user assigned identity and the scope of the role definition" } variable "resource_group_name" { type = string default = null + description = "The resource group to deploy the container to and where the role definition will be created" } -variable "log_analytics_workspace_id" { +variable "container_app_environment_id" { type = string default = null + description = "The ID of the container app environment to deploy the container to, if not provided, a new container app environment will be created" } -variable "container_app_environment_id" { +variable "log_analytics_workspace_id" { type = string default = null + description = "The ID of the log analytics workspace to send logs to, if not provided, log analytics will be created" } variable "cpu" { type = string default = "1.0" + description = "The CPU to allocate to the container" } variable "memory" { type = string default = "2Gi" + description = "The memory to allocate to the container" } variable "image" { type = string default = null + description = "The image to deploy, if not provided, the latest image of the integration will be deployed" } variable "min_replicas" { type = number default = 1 + description = "The minimum number of replicas to deploy" } variable "max_replicas" { - type = number - default = 1 + type = number + default = 1 + description = "The maximum number of replicas to deploy" } variable "permissions" { @@ -131,14 +150,11 @@ variable "permissions" { not_actions = optional(list(string)) not_data_actions = optional(list(string)) }) + description = "The permissions to grant to the user assigned identity" } variable "permissions_scope" { - type = list(string) + type = list(string) default = null -} - -variable "user_assigned_identity_ids" { - type = list(string) - default = [] + description = "The scope of the permissions" } \ No newline at end of file From 4fe28790d1d9b53764c783f865f5f9c8cff82415 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 12:33:58 +0300 Subject: [PATCH 53/95] adjust --- deployment/terraform/azure/containerapp/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/terraform/azure/containerapp/main.tf b/deployment/terraform/azure/containerapp/main.tf index 8a77721e82..57cb263ba2 100644 --- a/deployment/terraform/azure/containerapp/main.tf +++ b/deployment/terraform/azure/containerapp/main.tf @@ -29,7 +29,7 @@ module "port_ocean_container_app" { image = var.image min_replicas = var.min_replicas max_replicas = var.max_replicas - user_assigned_identity_ids = module.port_ocean_authorization.user_assigned_identity_id + user_assigned_identity_ids = [module.port_ocean_authorization.user_assigned_identity_id] user_assigned_client_id = module.port_ocean_authorization.user_assigned_identity_client_id additional_secrets = var.additional_secrets additional_environment_variables = var.additional_environment_variables From 11395e15a7c8aacf9790ad78d454fc643ad56933 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 12:35:21 +0300 Subject: [PATCH 54/95] adjust endpoint --- .../azure/containerapp/examples/azure-integration/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf b/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf index 7bf8406d4d..6a8d31a364 100644 --- a/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf +++ b/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf @@ -20,7 +20,6 @@ module "ocean_integration" { subscription_id = "/subscriptions/xxxxxx" location = "East US 2" - image = "ghcr.io/port-labs/port-ocean-azure:v0.1.0rc11" permissions = { @@ -67,7 +66,7 @@ resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event ] event_delivery_schema = "CloudEventSchemaV1_0" webhook_endpoint { - url = "https://${module.ocean_integration.container_app_latest_fqdn}/integration/azure/events" + url = "https://${module.ocean_integration.container_app_latest_fqdn}/integration/events" } advanced_filtering_on_arrays_enabled = true advanced_filter { @@ -78,6 +77,7 @@ resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event "Microsoft.Storage/storageAccounts", "Microsoft.ContainerService/managedClusters", "Microsoft.Network/loadBalancers", + "Microsoft.Compute/virtualMachine", "Microsoft.Resources/subscriptions/resourceGroups", ] } From 6892fe09f85e7c7bb1d81931615768fe0c5bfa44 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 14:56:59 +0300 Subject: [PATCH 55/95] lower case the resource group name as well --- .../.port/resources/port-app-config.yaml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index 6345541286..c6a1127755 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -8,7 +8,7 @@ resources: entity: mappings: # lowercase only the resourceGroups namespace so other azure resources will be able to relate to it - identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"resource_group"' properties: @@ -23,8 +23,8 @@ resources: port: entity: mappings: - # lowercase only the resourceGroups namespace to adjust with other azure api resources - identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"container_app"' properties: @@ -45,8 +45,8 @@ resources: port: entity: mappings: - # lowercase only the resourceGroups namespace to adjust with other azure api resources - identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"aks"' properties: @@ -81,8 +81,8 @@ resources: port: entity: mappings: - # lowercase only the resourceGroups namespace to adjust with other azure api resources - identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"load_balancer"' properties: @@ -106,8 +106,8 @@ resources: port: entity: mappings: - # lowercase only the resourceGroups namespace to adjust with other azure api resources - identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"storage_account"' properties: @@ -130,12 +130,12 @@ resources: selector: query: "true" # azure resource api version to query - api_version: '2023-02-01' + api_version: '2023-03-01' port: entity: mappings: - # lowercase only the resourceGroups namespace to adjust with other azure api resources - identifier: '.id | split("/") | .[3] |= ascii_downcase | join("/")' + # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"virtual_machine"' properties: From c60a3d86225f23d53c3296a9eed87158f8dd2d2d Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:03:40 +0300 Subject: [PATCH 56/95] add support for batch response --- integrations/azure/azure_integration/ocean.py | 55 ++++++++++--------- integrations/azure/azure_integration/utils.py | 24 ++++++++ 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index 2ddd416a67..bbf470c9f3 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -1,6 +1,7 @@ from fastapi import Request from loguru import logger from port_ocean.context.ocean import ocean +from port_ocean.context.event import event from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE from azure.identity.aio import DefaultAzureCredential from azure.core.exceptions import ResourceNotFoundError @@ -11,6 +12,7 @@ get_integration_subscription_id, get_port_resource_configuration_by_kind, resolve_resource_type_from_cloud_event, + batch_iterate_resources_list, ) from azure_integration.azure_patch import list_resources @@ -26,35 +28,35 @@ async def resync_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: logger.debug("Skipping resync", kind=kind) return - resource_config = await get_port_resource_configuration_by_kind(kind) - api_version = resource_config["selector"]["api_version"] async with DefaultAzureCredential() as credential: async with ResourceManagementClient( credential=credential, subscription_id=get_integration_subscription_id() ) as client: - logger.debug("Listing resources", kind=kind, api_version=api_version) - async for resource in list_resources(client, kind, api_version): - yield resource.as_dict() + logger.debug( + "Listing resources", + kind=kind, + api_version=event.resource_config.selector.api_version, + ) + async for resources_batch in batch_iterate_resources_list( + list_resources, + resources_client=client, + resource_type=kind, + api_version=event.resource_config.selector.api_version, + ): + yield resources_batch @ocean.on_resync(kind="Microsoft.Resources/resourceGroups") async def resync_resource_groups(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - resource_config = await get_port_resource_configuration_by_kind(kind) - api_version = resource_config["selector"]["api_version"] async with DefaultAzureCredential() as credential: async with ResourceManagementClient( credential=credential, subscription_id=get_integration_subscription_id() ) as client: - async for resource_group in client.resource_groups.list( - api_version=api_version + async for resource_groups_batch in batch_iterate_resources_list( + client.resource_groups.list, + api_version=event.resource_config.selector.api_version, ): - logger.debug( - "Found resource group", - resource_group_id=resource_group.id, - kind=kind, - api_version=api_version, - ) - yield resource_group.as_dict() + yield resource_groups_batch @ocean.router.post("/events") @@ -65,21 +67,22 @@ async def handle_events(request: Request): https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema """ - event = await request.json() + cloud_evnet = await request.json() logger.debug( "Received azure cloud event", - event_id=event["id"], - event_type=event["type"], - resource_provider=event["data"]["resourceProvider"], - operation_name=event["data"]["operationName"], + event_id=cloud_evnet["id"], + event_type=cloud_evnet["type"], + resource_provider=cloud_evnet["data"]["resourceProvider"], + operation_name=cloud_evnet["data"]["operationName"], ) - resource_type = resolve_resource_type_from_cloud_event(event) + resource_type = resolve_resource_type_from_cloud_event(cloud_evnet) if not resource_type: logger.warning( "Weren't able to resolve resource type from cloud event", - resource_uri=event["data"]["resourceUri"], + resource_uri=cloud_evnet["data"]["resourceUri"], ) return {"ok": False} + resource_config = await get_port_resource_configuration_by_kind(resource_type) if not resource_config: logger.debug( @@ -94,20 +97,20 @@ async def handle_events(request: Request): ) as client: logger.debug( "Querying full resource", - id=event["data"]["resourceUri"], + id=cloud_evnet["data"]["resourceUri"], kind=resource_type, api_version=resource_config["selector"]["api_version"], ) try: resource = await client.resources.get_by_id( - resource_id=event["data"]["resourceUri"], + resource_id=cloud_evnet["data"]["resourceUri"], api_version=resource_config["selector"]["api_version"], ) except ResourceNotFoundError: # TODO: delete from port once ocean adds support to delete per identifier logger.warning( "Resource not found", - id=event["data"]["resourceUri"], + id=cloud_evnet["data"]["resourceUri"], kind=resource_type, api_version=resource_config["selector"]["api_version"], ) diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index 1d2853274e..99a1cfe8ff 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -5,6 +5,8 @@ from azure_integration.overrides import AzurePortAppConfig +BATCH_SIZE = 20 + def get_integration_subscription_id() -> str: logic_settings = ocean.integration_config @@ -38,3 +40,25 @@ def resolve_resource_type_from_cloud_event(cloud_event: dict) -> str: return "" resource_type = f"{resource[6]}/{resource[7]}" return resource_type + + +async def batch_iterate_resources_list( + async_list_method: typing.Callable[..., typing.AsyncIterable], **kwargs +) -> typing.AsyncIterable: + """ + Iterates over the list method of the resources client and yields a list of resources. + + :param async_list_method: The list method of the resources client + :param kwargs: Any additional arguments that need to be passed to the list method + :return: A list of resources + """ + counter = 0 + resource_list = [] + async for resource in async_list_method(**kwargs): + counter += 1 + resource_list.append(resource.as_dict()) + if counter == BATCH_SIZE: + yield resource_list + counter = 0 + resource_list = [] + yield resource_list From ee21a83f7fb529b228063cc81cba7ad3d166ba06 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:04:10 +0300 Subject: [PATCH 57/95] bump ocean version to 0.2.1 --- integrations/azure/poetry.lock | 8 ++++---- integrations/azure/pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock index 4c9f29f521..4393a942ab 100644 --- a/integrations/azure/poetry.lock +++ b/integrations/azure/poetry.lock @@ -1328,13 +1328,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "port-ocean" -version = "0.1.3" +version = "0.2.1" description = "Port Ocean is a CLI tool for managing your Port projects." optional = false python-versions = ">=3.11,<4.0" files = [ - {file = "port_ocean-0.1.3-py3-none-any.whl", hash = "sha256:c4a02ab9b3e867c53b615aaeff02068ac06a3e8b449cffc962a4a38bba694a8f"}, - {file = "port_ocean-0.1.3.tar.gz", hash = "sha256:3ae6bd92a0da31f678b6f6157ce8a9364daebf24a0d7e683adffa338adba7330"}, + {file = "port_ocean-0.2.1-py3-none-any.whl", hash = "sha256:a97ca020eb44c050283f45de2dd3b85891fdc3f4c3536c4cd4bfef4ec4dffeae"}, + {file = "port_ocean-0.2.1.tar.gz", hash = "sha256:6bd532fc6642b366bf88ac429f55c225ffde365ad49e9901b8306869e2e55ff0"}, ] [package.dependencies] @@ -2039,4 +2039,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "1412e543a22721e792bfb8746caa8dc27b09de215e2f80b274c1bff41872b137" +content-hash = "1d7fcec7db18cfa35334523237754b273ed96ca255c396e243018a690020144e" diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml index 751302e5d1..d9d3ce97f3 100644 --- a/integrations/azure/pyproject.toml +++ b/integrations/azure/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Tom Tankilevitch "] [tool.poetry.dependencies] python = "^3.11" -port_ocean = {version = "^0.1.3", extras = ["cli"]} +port_ocean = {version = "^0.2.1", extras = ["cli"]} # due to patching the azure-mgmt-resource package, we need to use a specific version azure-mgmt-resource = "23.0.1" azure-identity = "^1.13.0" From 8e4df924ea4cc0e46ed632903019fc9719d52ac4 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:04:27 +0300 Subject: [PATCH 58/95] bump rc12 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 0d9ee16835..50f7c00bb1 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc11 +version: v0.1.0rc12 type: azure description: azure integration for Port Ocean icon: Cookiecutter From da7c122e9fd92da8c0fff863d43bc65834df09a9 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:06:06 +0300 Subject: [PATCH 59/95] rename directory from containerapp to container_app --- .../examples/azure-integration/providers.tf | 0 .../terraform/azure/{containerapp => container_app}/main.tf | 0 .../{containerapp => container_app}/modules/authorization/main.tf | 0 .../modules/authorization/outputs.tf | 0 .../modules/authorization/variables.tf | 0 .../{containerapp => container_app}/modules/container_app/main.tf | 0 .../modules/container_app/outputs.tf | 0 .../modules/container_app/variables.tf | 0 .../terraform/azure/{containerapp => container_app}/outputs.tf | 0 .../terraform/azure/{containerapp => container_app}/providers.tf | 0 .../terraform/azure/{containerapp => container_app}/variables.tf | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename deployment/terraform/azure/{containerapp => container_app}/examples/azure-integration/providers.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/main.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/modules/authorization/main.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/modules/authorization/outputs.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/modules/authorization/variables.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/modules/container_app/main.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/modules/container_app/outputs.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/modules/container_app/variables.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/outputs.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/providers.tf (100%) rename deployment/terraform/azure/{containerapp => container_app}/variables.tf (100%) diff --git a/deployment/terraform/azure/containerapp/examples/azure-integration/providers.tf b/deployment/terraform/azure/container_app/examples/azure-integration/providers.tf similarity index 100% rename from deployment/terraform/azure/containerapp/examples/azure-integration/providers.tf rename to deployment/terraform/azure/container_app/examples/azure-integration/providers.tf diff --git a/deployment/terraform/azure/containerapp/main.tf b/deployment/terraform/azure/container_app/main.tf similarity index 100% rename from deployment/terraform/azure/containerapp/main.tf rename to deployment/terraform/azure/container_app/main.tf diff --git a/deployment/terraform/azure/containerapp/modules/authorization/main.tf b/deployment/terraform/azure/container_app/modules/authorization/main.tf similarity index 100% rename from deployment/terraform/azure/containerapp/modules/authorization/main.tf rename to deployment/terraform/azure/container_app/modules/authorization/main.tf diff --git a/deployment/terraform/azure/containerapp/modules/authorization/outputs.tf b/deployment/terraform/azure/container_app/modules/authorization/outputs.tf similarity index 100% rename from deployment/terraform/azure/containerapp/modules/authorization/outputs.tf rename to deployment/terraform/azure/container_app/modules/authorization/outputs.tf diff --git a/deployment/terraform/azure/containerapp/modules/authorization/variables.tf b/deployment/terraform/azure/container_app/modules/authorization/variables.tf similarity index 100% rename from deployment/terraform/azure/containerapp/modules/authorization/variables.tf rename to deployment/terraform/azure/container_app/modules/authorization/variables.tf diff --git a/deployment/terraform/azure/containerapp/modules/container_app/main.tf b/deployment/terraform/azure/container_app/modules/container_app/main.tf similarity index 100% rename from deployment/terraform/azure/containerapp/modules/container_app/main.tf rename to deployment/terraform/azure/container_app/modules/container_app/main.tf diff --git a/deployment/terraform/azure/containerapp/modules/container_app/outputs.tf b/deployment/terraform/azure/container_app/modules/container_app/outputs.tf similarity index 100% rename from deployment/terraform/azure/containerapp/modules/container_app/outputs.tf rename to deployment/terraform/azure/container_app/modules/container_app/outputs.tf diff --git a/deployment/terraform/azure/containerapp/modules/container_app/variables.tf b/deployment/terraform/azure/container_app/modules/container_app/variables.tf similarity index 100% rename from deployment/terraform/azure/containerapp/modules/container_app/variables.tf rename to deployment/terraform/azure/container_app/modules/container_app/variables.tf diff --git a/deployment/terraform/azure/containerapp/outputs.tf b/deployment/terraform/azure/container_app/outputs.tf similarity index 100% rename from deployment/terraform/azure/containerapp/outputs.tf rename to deployment/terraform/azure/container_app/outputs.tf diff --git a/deployment/terraform/azure/containerapp/providers.tf b/deployment/terraform/azure/container_app/providers.tf similarity index 100% rename from deployment/terraform/azure/containerapp/providers.tf rename to deployment/terraform/azure/container_app/providers.tf diff --git a/deployment/terraform/azure/containerapp/variables.tf b/deployment/terraform/azure/container_app/variables.tf similarity index 100% rename from deployment/terraform/azure/containerapp/variables.tf rename to deployment/terraform/azure/container_app/variables.tf From e493352f2f170c046383433f7b500ed1f9271b2d Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:06:36 +0300 Subject: [PATCH 60/95] move main --- .../examples/azure-integration/main.tf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename deployment/terraform/azure/{containerapp => container_app}/examples/azure-integration/main.tf (100%) diff --git a/deployment/terraform/azure/containerapp/examples/azure-integration/main.tf b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf similarity index 100% rename from deployment/terraform/azure/containerapp/examples/azure-integration/main.tf rename to deployment/terraform/azure/container_app/examples/azure-integration/main.tf From 903bbb6ed146d97d1c59178ed0941dc901335cbb Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:12:36 +0300 Subject: [PATCH 61/95] move authorization directory outside of container_app directory to enable re-use --- deployment/terraform/azure/container_app/main.tf | 2 +- .../azure/{container_app => }/modules/authorization/main.tf | 0 .../azure/{container_app => }/modules/authorization/outputs.tf | 0 .../{container_app => }/modules/authorization/variables.tf | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename deployment/terraform/azure/{container_app => }/modules/authorization/main.tf (100%) rename deployment/terraform/azure/{container_app => }/modules/authorization/outputs.tf (100%) rename deployment/terraform/azure/{container_app => }/modules/authorization/variables.tf (100%) diff --git a/deployment/terraform/azure/container_app/main.tf b/deployment/terraform/azure/container_app/main.tf index 57cb263ba2..7c720d5a37 100644 --- a/deployment/terraform/azure/container_app/main.tf +++ b/deployment/terraform/azure/container_app/main.tf @@ -9,7 +9,7 @@ resource "azurerm_resource_group" "ocean-rg" { } module "port_ocean_authorization" { - source = "./modules/authorization" + source = "../modules/authorization" location = var.location resource_group_name = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name integration = var.integration diff --git a/deployment/terraform/azure/container_app/modules/authorization/main.tf b/deployment/terraform/azure/modules/authorization/main.tf similarity index 100% rename from deployment/terraform/azure/container_app/modules/authorization/main.tf rename to deployment/terraform/azure/modules/authorization/main.tf diff --git a/deployment/terraform/azure/container_app/modules/authorization/outputs.tf b/deployment/terraform/azure/modules/authorization/outputs.tf similarity index 100% rename from deployment/terraform/azure/container_app/modules/authorization/outputs.tf rename to deployment/terraform/azure/modules/authorization/outputs.tf diff --git a/deployment/terraform/azure/container_app/modules/authorization/variables.tf b/deployment/terraform/azure/modules/authorization/variables.tf similarity index 100% rename from deployment/terraform/azure/container_app/modules/authorization/variables.tf rename to deployment/terraform/azure/modules/authorization/variables.tf From fd8abd54f49968d6dc7b1900eedf3302d7afdd4f Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:13:32 +0300 Subject: [PATCH 62/95] return debugging stuff --- port_ocean/consumers/kafka_consumer.py | 3 +-- port_ocean/core/handlers/port_app_config/base.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/port_ocean/consumers/kafka_consumer.py b/port_ocean/consumers/kafka_consumer.py index 7b033ff86c..bf15448730 100644 --- a/port_ocean/consumers/kafka_consumer.py +++ b/port_ocean/consumers/kafka_consumer.py @@ -53,8 +53,7 @@ def __init__( "group.id": "no-security", "enable.auto.commit": "false", } - # TODO: remove debug - logger.debug(f"Kafka config", kafka_config=kafka_config) + self.consumer = Consumer(kafka_config) def _handle_message(self, raw_msg: Message) -> None: diff --git a/port_ocean/core/handlers/port_app_config/base.py b/port_ocean/core/handlers/port_app_config/base.py index 31fa6809ca..e02ed8c65f 100644 --- a/port_ocean/core/handlers/port_app_config/base.py +++ b/port_ocean/core/handlers/port_app_config/base.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Any +from typing import Type, Any from port_ocean.context.event import event from port_ocean.core.base import BaseWithContext @@ -7,7 +7,7 @@ class BasePortAppConfig(BaseWithContext): - CONFIG_CLASS: PortAppConfig = PortAppConfig + CONFIG_CLASS: Type[PortAppConfig] = PortAppConfig @abstractmethod async def _get_port_app_config(self) -> dict[str, Any]: From 40427d2a316958a9b50ad902d10e2acaa0ed699c Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 15:15:54 +0300 Subject: [PATCH 63/95] add empty line to end of files --- .../azure/container_app/examples/azure-integration/main.tf | 2 +- deployment/terraform/azure/container_app/main.tf | 2 +- .../azure/container_app/modules/container_app/outputs.tf | 2 +- .../azure/container_app/modules/container_app/variables.tf | 2 +- deployment/terraform/azure/modules/authorization/outputs.tf | 2 +- deployment/terraform/azure/modules/authorization/variables.tf | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf index 6a8d31a364..f0339ad713 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf @@ -92,4 +92,4 @@ resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event type = "Static" value = "azure" } -} \ No newline at end of file +} diff --git a/deployment/terraform/azure/container_app/main.tf b/deployment/terraform/azure/container_app/main.tf index 7c720d5a37..90e56aedd5 100644 --- a/deployment/terraform/azure/container_app/main.tf +++ b/deployment/terraform/azure/container_app/main.tf @@ -33,4 +33,4 @@ module "port_ocean_container_app" { user_assigned_client_id = module.port_ocean_authorization.user_assigned_identity_client_id additional_secrets = var.additional_secrets additional_environment_variables = var.additional_environment_variables -} \ No newline at end of file +} diff --git a/deployment/terraform/azure/container_app/modules/container_app/outputs.tf b/deployment/terraform/azure/container_app/modules/container_app/outputs.tf index 84935ac180..ebab923973 100644 --- a/deployment/terraform/azure/container_app/modules/container_app/outputs.tf +++ b/deployment/terraform/azure/container_app/modules/container_app/outputs.tf @@ -8,4 +8,4 @@ output "container_app_outbound_ip_addresses" { output "container_latest_revision_name" { value = azurerm_container_app.ocean-container-app.latest_revision_name -} \ No newline at end of file +} diff --git a/deployment/terraform/azure/container_app/modules/container_app/variables.tf b/deployment/terraform/azure/container_app/modules/container_app/variables.tf index 6506822c5b..b51bc93031 100644 --- a/deployment/terraform/azure/container_app/modules/container_app/variables.tf +++ b/deployment/terraform/azure/container_app/modules/container_app/variables.tf @@ -147,4 +147,4 @@ variable "user_assigned_client_id" { type = string default = null description = "The client ID of the user assigned identity to assign to the container" -} \ No newline at end of file +} diff --git a/deployment/terraform/azure/modules/authorization/outputs.tf b/deployment/terraform/azure/modules/authorization/outputs.tf index 87442b7eab..72cb0cf946 100644 --- a/deployment/terraform/azure/modules/authorization/outputs.tf +++ b/deployment/terraform/azure/modules/authorization/outputs.tf @@ -8,4 +8,4 @@ output "user_assigned_identity_client_id" { output "subscription_id" { value = local.subscription_id -} \ No newline at end of file +} diff --git a/deployment/terraform/azure/modules/authorization/variables.tf b/deployment/terraform/azure/modules/authorization/variables.tf index be3cc0f10d..0e182ad235 100644 --- a/deployment/terraform/azure/modules/authorization/variables.tf +++ b/deployment/terraform/azure/modules/authorization/variables.tf @@ -45,4 +45,4 @@ variable "permissions_scope" { type = list(string) default = null description = "The scope of the permissions" -} \ No newline at end of file +} From e747d2fc9dcffa49acaa8d0c3e21cd3a2cb5ef53 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 16:53:36 +0300 Subject: [PATCH 64/95] rename method --- integrations/azure/azure_integration/ocean.py | 6 +++--- integrations/azure/azure_integration/utils.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index bbf470c9f3..5912c5494e 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -12,7 +12,7 @@ get_integration_subscription_id, get_port_resource_configuration_by_kind, resolve_resource_type_from_cloud_event, - batch_iterate_resources_list, + batch_resources_iterator, ) from azure_integration.azure_patch import list_resources @@ -37,7 +37,7 @@ async def resync_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: kind=kind, api_version=event.resource_config.selector.api_version, ) - async for resources_batch in batch_iterate_resources_list( + async for resources_batch in batch_resources_iterator( list_resources, resources_client=client, resource_type=kind, @@ -52,7 +52,7 @@ async def resync_resource_groups(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: async with ResourceManagementClient( credential=credential, subscription_id=get_integration_subscription_id() ) as client: - async for resource_groups_batch in batch_iterate_resources_list( + async for resource_groups_batch in batch_resources_iterator( client.resource_groups.list, api_version=event.resource_config.selector.api_version, ): diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index 99a1cfe8ff..2c92540b72 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -42,7 +42,7 @@ def resolve_resource_type_from_cloud_event(cloud_event: dict) -> str: return resource_type -async def batch_iterate_resources_list( +async def batch_resources_iterator( async_list_method: typing.Callable[..., typing.AsyncIterable], **kwargs ) -> typing.AsyncIterable: """ From 9a9beeaea0165f7868e2c28350ac5274af7f1a19 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 10 Aug 2023 16:59:12 +0300 Subject: [PATCH 65/95] missing __init__.py --- integrations/azure/azure_integration/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 integrations/azure/azure_integration/__init__.py diff --git a/integrations/azure/azure_integration/__init__.py b/integrations/azure/azure_integration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From faac86970e6e854cbf8f42107ad3fdda814c7b57 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sat, 12 Aug 2023 01:55:11 +0300 Subject: [PATCH 66/95] removed cors middleware --- port_ocean/ocean.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/port_ocean/ocean.py b/port_ocean/ocean.py index aa6c77d2ea..30463b91ee 100644 --- a/port_ocean/ocean.py +++ b/port_ocean/ocean.py @@ -30,13 +30,6 @@ def __init__( ): initialize_port_ocean_context(self) self.fast_api_app = app or FastAPI() - self.fast_api_app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) self.fast_api_app.middleware("http")(request_handler) self.config = IntegrationConfiguration(base_path="./") From 47a8024983de05f404130d0da10d89c623126547 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sat, 12 Aug 2023 01:55:32 +0300 Subject: [PATCH 67/95] CR comments and added support for extension resources --- .../azure/.port/resources/blueprints.json | 204 +++++++++------ .../.port/resources/port-app-config.yaml | 167 +++++++----- integrations/azure/.port/spec.yaml | 9 +- .../azure/azure_integration/azure_patch.py | 77 ++++-- integrations/azure/azure_integration/ocean.py | 237 +++++++++++++----- .../azure/azure_integration/overrides.py | 8 +- integrations/azure/azure_integration/utils.py | 109 ++++++-- integrations/azure/config.yaml | 5 +- integrations/azure/debug.py | 141 +++++++++++ integrations/azure/integration.py | 3 +- integrations/azure/poetry.lock | 33 ++- integrations/azure/pyproject.toml | 2 + 12 files changed, 751 insertions(+), 244 deletions(-) diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index d6b1c6683e..c00049cc6c 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -1,6 +1,6 @@ [ { - "identifier": "resource_group", + "identifier": "resourceGroup", "description": "This blueprint represents an Azure Resource Group in our software catalog", "title": "Resource Group", "icon": "Azure", @@ -10,7 +10,7 @@ "title": "Location", "type": "string" }, - "provisioning_state": { + "provisioningState": { "title": "Provisioning State", "type": "string" }, @@ -22,7 +22,7 @@ } }, { - "identifier": "container_app", + "identifier": "containerApp", "description": "This blueprint represents an Azure Container App in our software catalog", "title": "Container App", "icon": "Azure", @@ -32,27 +32,27 @@ "title": "Location", "type": "string" }, - "provisioning_state": { + "provisioningState": { "title": "Provisioning State", "type": "string" }, - "outbound_ip_addresses": { + "outboundIpAddresses": { "title": "Outbound IP Addresses", "type": "array" }, - "external_ingress": { + "externalIngress": { "title": "External Ingress", "type": "boolean" }, - "host_name": { + "hostName": { "title": "Host Name", "type": "string" }, - "min_replicas": { + "minReplicas": { "title": "Min Replicas", "type": "number" }, - "max_replicas": { + "maxReplicas": { "title": "Max Replicas", "type": "number" } @@ -62,8 +62,9 @@ "mirrorProperties": {}, "calculationProperties": {}, "relations": { - "resource_group": { - "target": "resource_group", + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", "required": true, "many": false } @@ -80,23 +81,23 @@ "title": "Location", "type": "string" }, - "provisioning_state": { + "provisioningState": { "title": "Provisioning State", "type": "string" }, - "power_state": { + "powerState": { "title": "Power State", "type": "string" }, - "kubernetes_version": { + "kubernetesVersion": { "title": "Kubernetes Version", "type": "string" }, - "current_kubernetes_version": { + "currentKubernetesVersion": { "title": "Current Kubernetes Version", "type": "string" }, - "dns_prefix": { + "dnsPrefix": { "title": "DNS Prefix", "type": "string" }, @@ -104,47 +105,47 @@ "title": "FQDN", "type": "string" }, - "node_resource_group": { + "nodeResourceGroup": { "title": "Node Resource Group", "type": "string" }, - "enable_rbac": { + "enableRBAC": { "title": "Enable RBAC", "type": "boolean" }, - "support_plan": { + "supportPlan": { "title": "Support Plan", "type": "string" }, - "network_plugin": { + "networkPlugin": { "title": "Network Plugin", "type": "string" }, - "pod_cidr": { + "podCIDR": { "title": "Pod CIDR", "type": "string" }, - "service_cidr": { + "serviceCIDR": { "title": "Service CIDR", "type": "string" }, - "dns_service_ip": { + "dnsServiceIp": { "title": "DNS Service IP", "type": "string" }, - "outbound_type": { + "outboundType": { "title": "Outbound Type", "type": "string" }, - "load_balancer_sku": { + "loadBalancerSKU": { "title": "Load Balancer SKU", "type": "string" }, - "max_agent_pools": { + "maxAgentPools": { "title": "Max Agent Pools", "type": "number" }, - "sku_tier": { + "skuTier": { "title": "Tier", "type": "string" } @@ -154,15 +155,16 @@ "mirrorProperties": {}, "calculationProperties": {}, "relations": { - "resource_group": { - "target": "resource_group", + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", "required": true, "many": false } } }, { - "identifier": "load_balancer", + "identifier": "loadBalancer", "description": "This blueprint represents an Azure Load Balancer in our software catalog", "title": "Load Balancer", "icon": "Azure", @@ -176,31 +178,31 @@ "title": "Tags", "type": "object" }, - "provisioning_state": { + "provisioningState": { "title": "Provisioning State", "type": "string" }, - "frontend_ip_resource_ids": { + "frontendIpResourceIds": { "title": "Frontend IP Resource IDs", "type": "array" }, - "backend_address_pools_resource_ids": { + "backendAddressPoolsResourceIds": { "title": "Backend Address Pools Resource IDs", "type": "array" }, - "load_balancing_rules_resource_ids": { + "loadBalancingRulesResourceIds": { "title": "Load Balancing Rules Resource IDs", "type": "array" }, - "probes_resource_ids": { + "probesResourceIds": { "title": "Probes Resource IDs", "type": "array" }, - "inbound_nat_rules_resource_ids": { + "inboundNatRulesResourceIds": { "title": "Inbound NAT Rules Resource IDs", "type": "array" }, - "inbound_nat_pools_resource_ids": { + "inboundNatPoolsResourceIds": { "title": "Inbound NAT Pools Resource IDs", "type": "array" } @@ -210,15 +212,16 @@ "mirrorProperties": {}, "calculationProperties": {}, "relations": { - "resource_group": { - "target": "resource_group", + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", "required": true, "many": false } } }, { - "identifier": "storage_account", + "identifier": "storageAccount", "description": "This blueprint represents an Azure Storage Account in our software catalog", "title": "Storage Account", "icon": "Azure", @@ -228,36 +231,36 @@ "title": "Location", "type": "string" }, - "provisioning_state": { + "provisioningState": { "title": "Provisioning State", "type": "string" }, - "creation_time": { + "creationTime": { "title": "Creation Time", "type": "string" }, - "is_hns_enabled": { + "isHnsEnabled": { "title": "Is HNS Enabled", "type": "boolean", "default": false }, - "file_encryption_enabled": { + "fileEncryptionEnabled": { "title": "File Encryption Enabled", "type": "boolean" }, - "blob_encryption_enabled": { + "blobEncryptionEnabled": { "title": "Blob Encryption Enabled", "type": "boolean" }, - "primary_location": { + "primaryLocation": { "title": "Primary Location", "type": "string" }, - "secondary_location": { + "secondaryLocation": { "title": "Secondary Location", "type": "string" }, - "status_of_primary": { + "statusOfPrimary": { "title": "Status of Primary", "type": "string", "enum": [ @@ -269,7 +272,7 @@ "available": "green" } }, - "status_of_secondary": { + "statusOfSecondary": { "title": "Status of Secondary", "type": "string", "enum": [ @@ -285,7 +288,7 @@ "title": "Tags", "type": "object" }, - "allow_blob_public_access": { + "allowBlobPublicAccess": { "title": "Allow Blob Public Access", "type": "boolean" } @@ -295,15 +298,77 @@ "mirrorProperties": {}, "calculationProperties": {}, "relations": { - "resource_group": { - "target": "resource_group", + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", "required": true, "many": false } } }, { - "identifier": "virtual_machine", + "identifier": "storageContainer", + "description": "This blueprint represents an Azure Storage Container in our software catalog", + "title": "Storage Container", + "icon": "S3", + "schema": { + "properties": { + "publicAccess": { + "title": "Public Access", + "type": "string" + }, + "hasImmutabilityPolicy": { + "title": "Has Immutability Policy", + "type": "boolean" + }, + "hasLegalHold": { + "title": "Has Legal Hold", + "type": "boolean" + }, + "deleted": { + "title": "Deleted", + "type": "boolean" + }, + "deletedTime": { + "title": "Deleted Time", + "type": "string" + }, + "remainingRetentionDays": { + "title": "Remaining Retention Days", + "type": "number" + }, + "leaseStatus": { + "title": "Lease Status", + "type": "string" + }, + "leaseState": { + "title": "Lease State", + "type": "string" + }, + "defaultEncryptionScope": { + "title": "Default Encryption Scope", + "type": "string" + }, + "version": { + "title": "Version", + "type": "string" + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "storageAccount": { + "target": "storageAccount", + "title": "Storage Account", + "required": true, + "many": false + } + } + }, + { + "identifier": "virtualMachine", "description": "This blueprint represents an Azure Virtual Machine in our software catalog", "title": "Virtual Machine", "icon": "Azure", @@ -313,58 +378,54 @@ "title": "Location", "type": "string" }, - "provisioning_state": { + "provisioningState": { "title": "Provisioning State", "type": "string" }, - "vm_size": { + "vmSize": { "title": "VM Size", "type": "string" }, - "os_disk_name": { + "osDiskName": { "title": "OS Disk Name", "type": "string" }, - "os_disk_type": { + "osDiskType": { "title": "OS Disk Type", "type": "string" }, - "os_disk_caching": { + "osDiskCaching": { "title": "OS Disk Caching", "type": "string" }, - "os_disk_size_gb": { + "osDiskSizeGB": { "title": "OS Disk Size GB", "type": "number" }, - "os_disk_create_option": { + "osDiskCreateOption": { "title": "OS Disk Create Option", "type": "string" }, - "network_interface_ids": { + "networkInterfaceIds": { "title": "Network Interface IDs", "type": "array" }, - "license_type": { + "licenseType": { "title": "License Type", "type": "string" }, - "vm_os_profile": { + "vmOsProfile": { "title": "VM OS Profile", "type": "object" }, - "vm_hardware_profile": { + "vmHardwareProfile": { "title": "VM Hardware Profile", "type": "object" }, - "vm_storage_profile": { + "vmStorageProfile": { "title": "VM Storage Profile", "type": "object" }, - "zones": { - "title": "Zones", - "type": "array" - }, "tags": { "title": "Tags", "type": "object" @@ -375,8 +436,9 @@ "mirrorProperties": {}, "calculationProperties": {}, "relations": { - "resource_group": { - "target": "resource_group", + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", "required": true, "many": false } diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index c6a1127755..70706d1455 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -3,45 +3,47 @@ resources: selector: query: "true" # azure resource api version to query - api_version: '2022-09-01' + apiVersion: '2022-09-01' port: entity: mappings: # lowercase only the resourceGroups namespace so other azure resources will be able to relate to it identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name - blueprint: '"resource_group"' + blueprint: '"resourceGroup"' properties: location: .location - provisioning_state: .properties.provisioning_state + # the provisioning state property is returned in lower case when using the SDK and in camelCase when using the REST API + # therefore supporting both (for users who use the SDK) + provisioningState: .properties.provisioningState + .properties.provisioning_state tags: .tags - kind: Microsoft.App/containerApps selector: query: "true" # azure resource api version to query - api_version: '2022-03-01' + apiVersion: '2022-03-01' port: entity: mappings: # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name - blueprint: '"container_app"' + blueprint: '"containerApp"' properties: location: .location - provisioning_state: .properties.provisioningState - outbound_ip_addresses: .properties.outboundIpAddresses - external_ingress: .properties.configuration.ingress.external - host_name: .properties.configuration.ingress.fqdn - min_replicas: .properties.template.scale.minReplicas - max_replicas: .properties.template.scale.maxReplicas + provisioningState: .properties.provisioningState + outboundIpAddresses: .properties.outboundIpAddresses + externalIngress: .properties.configuration.ingress.external + hostName: .properties.configuration.ingress.fqdn + minReplicas: .properties.template.scale.minReplicas + maxReplicas: .properties.template.scale.maxReplicas relations: - resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.ContainerService/managedClusters selector: query: "true" # azure resource api version to query - api_version: '2023-05-01' + apiVersion: '2023-05-01' port: entity: mappings: @@ -51,109 +53,136 @@ resources: blueprint: '"aks"' properties: location: .location - provisioning_state: .properties.provisioningState - power_state: .properties.powerState.code - kubernetes_version: .properties.kubernetesVersion - current_kubernetes_version: .properties.currentKubernetesVersion - dns_prefix: .properties.dnsPrefix + provisioningState: .properties.provisioningState + powerState: .properties.powerState.code + kubernetesVersion: .properties.kubernetesVersion + currentKubernetesVersion: .properties.currentKubernetesVersion + dnsPrefix: .properties.dnsPrefix fqdn: .properties.fqdn - node_resource_group: .properties.nodeResourceGroup - enable_rbac: .properties.enableRBAC - support_plan: .properties.supportPlan - network_plugin: .properties.networkProfile.networkPlugin - pod_cidr: .properties.networkProfile.podCidr - service_cidr: .properties.networkProfile.serviceCidr - dns_service_ip: .properties.networkProfile.dnsServiceIP - outbound_type: .properties.networkProfile.outboundType - load_balancer_sku: .properties.networkProfile.loadBalancerSku - max_agent_pools: .properties.maxAgentPools - sku_tier: .properties.sku.tier + nodeResourceGroup: .properties.nodeResourceGroup + enableRBAC: .properties.enableRBAC + supportPlan: .properties.supportPlan + networkPlugin: .properties.networkProfile.networkPlugin + podCIDR: .properties.networkProfile.podCidr + serviceCIDR: .properties.networkProfile.serviceCidr + dnsServiceIp: .properties.networkProfile.dnsServiceIP + outboundType: .properties.networkProfile.outboundType + loadBalancerSKU: .properties.networkProfile.loadBalancerSku + maxAgentPools: .properties.maxAgentPools + skuTier: .properties.sku.tier relations: # resolve resource group id from aks id # AKS id contains the resourcegroups namespace instead of resourceGroups therefore we need to lowercase # all other resourceGroups namespaces of other azure resources so we will be able to relate them to resource group blueprint - resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Network/loadBalancers selector: query: "true" # azure resource api version to query - api_version: '2023-02-01' + apiVersion: '2023-02-01' port: entity: mappings: # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name - blueprint: '"load_balancer"' + blueprint: '"loadBalancer"' properties: location: .location - provisioning_state: .properties.provisioningState + provisioningState: .properties.provisioningState tags: .tags - frontend_ip_resource_ids: .properties.frontendIPConfigurations[].id - backend_address_pool_resource_ids: .properties.backendAddressPools[].id - load_balancing_rules_resource_ids: .properties.loadBalancingRules[].id - probes_resource_ids: .properties.probes[].id - inbound_nat_rules_resource_ids: .properties.inboundNatRules[].id - inbound_nat_pools_resource_ids: .properties.inboundNatPools[].id + frontendIpResourceIds: .properties.frontendIPConfigurations[].id + backendAddressPoolResourceIds: .properties.backendAddressPools[].id + loadBalancingRulesResourceIds: .properties.loadBalancingRules[].id + probesResourceIds: .properties.probes[].id + inboundNatRulesResourceIds: .properties.inboundNatRules[].id + inboundNatPoolsResourceIds: .properties.inboundNatPools[].id relations: # resolve resource group id from load balancer id - resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Storage/storageAccounts selector: query: "true" # azure resource api version to query - api_version: '2023-01-01' + apiVersion: '2023-01-01' port: entity: mappings: # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name - blueprint: '"storage_account"' + blueprint: '"storageAccount"' properties: location: .location - provisioning_state: .properties.provisioningState - creation_time: .properties.creationTime - is_hns_enabled: .properties.isHnsEnabled - file_encryption_enabled: .properties.encryption.services.file.enabled - blob_encryption_enabled: .properties.encryption.services.blob.enabled - primary_location: .properties.primaryLocation - secondary_location: .properties.secondaryLocation - status_of_primary: .properties.statusOfPrimary - status_of_secondary: .properties.statusOfSecondary - allow_blob_public_access: .properties.allowBlobPublicAccess + provisioningState: .properties.provisioningState + creationTime: .properties.creationTime + isHnsEnabled: .properties.isHnsEnabled + fileEncryptionEnabled: .properties.encryption.services.file.enabled + blobEncryptionEnabled: .properties.encryption.services.blob.enabled + primaryLocation: .properties.primaryLocation + secondaryLocation: .properties.secondaryLocation + statusOfPrimary: .properties.statusOfPrimary + statusOfSecondary: .properties.statusOfSecondary + allowBlobPublicAccess: .properties.allowBlobPublicAccess tags: .tags relations: # resolve resource group id from storage account id - resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' + + - kind: Microsoft.Storage/storageAccounts/blobServices/containers + selector: + query: "true" + # azure resource api version to query + apiVersion: '2023-01-01' + port: + entity: + mappings: + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"storageContainer"' + properties: + publicAccess: .properties.publicAccess + hasImmutabilityPolicy: .properties.hasImmutabilityPolicy + hasLegalHold: .properties.hasLegalHold + deleted: .properties.deleted + deletedTime: .properties.deletedTime + remainingRetentionDays: .properties.remainingRetentionDays + leaseStatus: .properties.leaseStatus + leaseState: .properties.leaseState + defaultEncryptionScope: .properties.defaultEncryptionScope + version: .properties.version + relations: + # resolve storage account id from container id + storageAccount: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:-4] |join("/")' + + - kind: Microsoft.Compute/virtualMachines selector: query: "true" # azure resource api version to query - api_version: '2023-03-01' + apiVersion: '2023-03-01' port: entity: mappings: # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name - blueprint: '"virtual_machine"' + blueprint: '"virtualMachine"' properties: location: .location - provisioning_state: .properties.provisioningState - vm_size: .properties.hardwareProfile.vmSize - os_disk_name: .properties.storageProfile.osDisk.name - os_type: .properties.storageProfile.osDisk.osType - os_disk_caching: .properties.storageProfile.osDisk.caching - os_disk_size_gb: .properties.storageProfile.osDisk.diskSizeGB - os_disk_create_option: .properties.storageProfile.osDisk.createOption - network_interface_ids: .properties.networkProfile.networkInterfaces[].id - license_type: .properties.licenseType - vm_os_profile: .properties.osProfile - vm_hardware_profile: .properties.hardwareProfile - vm_storage_profile: .properties.storageProfile - zones: .properties.zones + provisioningState: .properties.provisioningState + vmSize: .properties.hardwareProfile.vmSize + osDiskName: .properties.storageProfile.osDisk.name + osType: .properties.storageProfile.osDisk.osType + osDiskCaching: .properties.storageProfile.osDisk.caching + osDiskSizeGB: .properties.storageProfile.osDisk.diskSizeGB + osDiskCreateOption: .properties.storageProfile.osDisk.createOption + networkInterfaceIds: .properties.networkProfile.networkInterfaces[].id + licenseType: .properties.licenseType + vmOsProfile: .properties.osProfile + vmHardwareProfile: .properties.hardwareProfile + vmStorageProfile: .properties.storageProfile tags: .tags relations: # resolve resource group id from virtual machine id - resource_group: '.id | split("/") | .[3] |= ascii_downcase | .[:5] |join("/")' \ No newline at end of file + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:5] |join("/")' \ No newline at end of file diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 50f7c00bb1..067ba5fbf9 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,13 +1,14 @@ version: v0.1.0rc12 type: azure description: azure integration for Port Ocean -icon: Cookiecutter +icon: Azure features: - type: exporter - section: Azure Providers + section: Cloud Providers resources: - - kind: - - kind: + - kind: resource_group + - kind: container_app + - kind: aks configurations: - name: subscriptionId required: true diff --git a/integrations/azure/azure_integration/azure_patch.py b/integrations/azure/azure_integration/azure_patch.py index 9634c88731..4909446354 100644 --- a/integrations/azure/azure_integration/azure_patch.py +++ b/integrations/azure/azure_integration/azure_patch.py @@ -1,9 +1,9 @@ -from typing import Any, Optional +from typing import Any, Optional, AsyncIterable import azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient from azure.core.rest import HttpRequest -from azure.mgmt.resource.resources.v2022_09_01.operations._operations import ( +from azure.mgmt.resource.resources.v2022_09_01.operations._operations import ( # type: ignore _format_url_section, _SERIALIZER, ) @@ -41,8 +41,10 @@ def build_full_resources_list_request_patch( ) resource_type = request.headers.pop("resource-type", None) + resource_url = request.headers.pop("resource-url", None) + + api_version = request.headers.pop("api-version", None) if resource_type: - api_version = request.headers.pop("api-version", None) # Build the url url = "/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion}" path_format_arguments = { @@ -56,27 +58,72 @@ def build_full_resources_list_request_patch( url = _format_url_section(url, **path_format_arguments) # Override the original url in the request request.url = url + elif resource_url: + # Build the url + url = "{resourceUrl}?api-version={apiVersion}" + path_format_arguments = { + "resourceUrl": _SERIALIZER.url("resource-url", resource_url, "str"), + "apiVersion": _SERIALIZER.url("api-version", api_version, "str"), + } + # Format the url + url = _format_url_section(url, **path_format_arguments) + # Override the original url in the request + request.url = url return request async def list_resources( - resources_client: ResourceManagementClient, resource_type: str, api_version: str -): + resources_client: ResourceManagementClient, + api_version: str, + resource_type: str = "", + resource_url: str = "", +) -> AsyncIterable[Any]: """ A list implementation that takes advantage of the patch implemented in this file. - To be able to use this implementation, the resource type and api version must be passed as headers to the request. + There are two ways to use this method: + 1. Pass the resource type and api version to the method, and it will query the resource provider + This is suitable for resource types that are not sub resources (Microsoft.Sql/servers) and can be queried in a + subscription scope. + example: + resource_type = "Microsoft.storage/storageAccounts" + api_version = "2023-01-01" + + Those params will result the following request: + (https://management.azure.com/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion}) + + 2. Pass the resource url and api version to the method, and it will query the resource provider + This is suitable for resource types that are sub resources (Microsoft.Sql/servers/databases) and can be queried + in a resource scope, which means that the resource id of the parent resource is required. + example: + Lets say we want to list all containers in a storage account. + The containers resource type is "Microsoft.storage/storageAccounts/blobServices/containers" + api_version = "2023-01-01" + resource_url = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}/blobServices/{blobService}/containers" + The resource url is the resource id of the parent resource + the resource type of the sub resource. + The resource url can be obtained by querying the parent resource and getting the id property from the response. + + Those params will result the following request: + (https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}/blobServices/{blobService}/containers?api-version={apiVersion}) """ # override the default version in the client to the version that we want to query resources_client.resources._config.api_version = api_version + if resource_type and resource_url: + raise ValueError("Only one of resource_type and resource_url can be passed") + + logger.debug( + "Listing resource", + resource_type=resource_type, + resource_url=resource_url, + api_version=api_version, + ) + async for resource in resources_client.resources.list( - headers={"resource-type": resource_type, "api-version": api_version} + headers={ + "resource-type": resource_type, + "resource-url": resource_url, + "api-version": api_version, + } ): - logger.debug( - "Found resource", - resource_id=resource.id, - kind=resource_type, - api_version=api_version, - ) yield resource @@ -84,8 +131,8 @@ async def list_resources( # This is done to be able to query the resource provider instead of the resources api # The original method is being called inside the list method in the resources client old_build_resources_list_request = ( - azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request + azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request # type: ignore ) -azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request = ( +azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request = ( # type: ignore build_full_resources_list_request_patch ) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index 5912c5494e..54db8d5fd6 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -1,7 +1,11 @@ -from fastapi import Request +import http + +from cloudevents.pydantic import CloudEvent +import fastapi from loguru import logger from port_ocean.context.ocean import ocean from port_ocean.context.event import event +from port_ocean.core.models import Entity from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE from azure.identity.aio import DefaultAzureCredential from azure.core.exceptions import ResourceNotFoundError @@ -9,34 +13,27 @@ from azure_integration.utils import ( + ResourceKindsWithSpecialHandling, + resource_client_context, get_integration_subscription_id, get_port_resource_configuration_by_kind, - resolve_resource_type_from_cloud_event, + resolve_resource_type_from_resource_uri, batch_resources_iterator, + is_sub_resource, + get_resource_kind_by_level, ) from azure_integration.azure_patch import list_resources -RESOURCE_KINDS_WITH_SPECIAL_HANDLING = [ - "Microsoft.Resources/resourceGroups", -] - - @ocean.on_resync() async def resync_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - if kind in RESOURCE_KINDS_WITH_SPECIAL_HANDLING: - logger.debug("Skipping resync", kind=kind) - return - - async with DefaultAzureCredential() as credential: - async with ResourceManagementClient( - credential=credential, subscription_id=get_integration_subscription_id() - ) as client: - logger.debug( - "Listing resources", - kind=kind, - api_version=event.resource_config.selector.api_version, - ) + with logger.contextualize(resource_kind=kind): + logger.info("Entered resync of base resources", kind=kind) + if kind in iter(ResourceKindsWithSpecialHandling) or is_sub_resource(kind): + logger.info("Kind is not a base resource, skipping resync", kind=kind) + return + + async with resource_client_context() as client: async for resources_batch in batch_resources_iterator( list_resources, resources_client=client, @@ -46,42 +43,156 @@ async def resync_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: yield resources_batch -@ocean.on_resync(kind="Microsoft.Resources/resourceGroups") +@ocean.on_resync(kind=ResourceKindsWithSpecialHandling.RESOURCE_GROUPS) async def resync_resource_groups(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - async with DefaultAzureCredential() as credential: - async with ResourceManagementClient( - credential=credential, subscription_id=get_integration_subscription_id() - ) as client: - async for resource_groups_batch in batch_resources_iterator( - client.resource_groups.list, + """ + Re-syncs resource groups, this is done separately because the resource groups api is different from the other apis + """ + with logger.contextualize(resource_kind=kind): + logger.info("Entered resync of resource groups", kind=kind) + async with DefaultAzureCredential() as credential: + async with ResourceManagementClient( + credential=credential, subscription_id=get_integration_subscription_id() + ) as client: + async for resource_groups_batch in batch_resources_iterator( + client.resource_groups.list, + api_version=event.resource_config.selector.api_version, + ): + yield resource_groups_batch + + +@ocean.on_resync() +async def resync_extension_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: + """ + Re-syncs sub resources + + The Resource Management API does not support listing extension resources + The only way to list extension resources is to query the base resource and get the extension resources from the response + This method take advantage of the fact that the resource id is the same as the resource url, + and uses it to query the base resource and with like that get the extension resources + + We support multiple levels of extension resources + For example: + Microsoft.Storage/storageAccounts/blobServices/containers is an extension resource, and also it parent + Microsoft.Storage/storageAccounts/blobServices is an extension resource of Microsoft.Storage/storageAccounts + Microsoft.Storage/storageAccounts is a base resource + + To get the resource "Microsoft.Storage/storageAccounts/blobServices/containers" we need to construct a query like this: + /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}/blobServices/default/containers + + So the way we do it is by splitting the resource kind by "/" and then query the highest level resource and then + loop over each of the extension resources in the hierarchy until we get to the last extension resource + + Obviously this has a performance impact, but it's the only way to get sub resources in a generic way, without + having the base resource ids + + :param kind: Resource kind + :return: Async generator of extension resources + """ + with logger.contextualize(resource_kind=kind): + logger.info("Entered resync of extension resources", kind=kind) + if not is_sub_resource(kind) or kind in iter(ResourceKindsWithSpecialHandling): + logger.info("Kind is not an extension resource, skipping resync", kind=kind) + return + + async with resource_client_context() as client: + base_resource_kind, _ = get_resource_kind_by_level(kind, 0) + async for resource in list_resources( + client, api_version=event.resource_config.selector.api_version, + resource_type=base_resource_kind, + ): + async for resource_batch in loop_over_extension_resource_kind( + client=client, + full_resource_kind=kind, + kind_level=1, + resource_id=resource.id, + api_version=event.resource_config.selector.api_version, + ): + yield resource_batch + + +async def loop_over_extension_resource_kind( + client: ResourceManagementClient, + full_resource_kind: str, + kind_level: int, + resource_id: str, + api_version: str, +): + """ + Loops over a extension resource kind and yields a batch of resources + + This method is called recursively until it reaches the last level of the resource kind, and then it yields a batch + of resources from the last level. + + :param client: The resource client + :param full_resource_kind: Full resource kind (Microsoft.Storage/storageAccounts/blobServices/containers) + :param kind_level: The level of the resource kind + (0 for Microsoft.Storage/storageAccounts, 1 for Microsoft.Storage/storageAccounts/blobServices, etc) + :param resource_id: The resource id of the parent resource, used to query the extension resources of the parent resource + (/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}) + :param api_version: The api version to use when querying the resources + """ + current_resource_kind, is_last_level = get_resource_kind_by_level( + full_resource_kind, kind_level + ) + current_resource_kind_suffix = current_resource_kind.split("/")[-1] + list_resource_url = f"{resource_id}/{current_resource_kind_suffix}" + logger.debug( + "Looping over resource kind", + resource_kind=current_resource_kind, + kind_level=kind_level, + parent_resource_id=resource_id, + api_version=api_version, + ) + if is_last_level: + async for resource_batch in batch_resources_iterator( + list_resources, + resources_client=client, + api_version=api_version, + resource_url=list_resource_url, + ): + yield resource_batch + else: + async for resource in list_resources( + client, + api_version=api_version, + resource_url=list_resource_url, + ): + async for resource_batch in loop_over_extension_resource_kind( + client=client, + full_resource_kind=full_resource_kind, + kind_level=kind_level + 1, + resource_id=resource.id, + api_version=api_version, ): - yield resource_groups_batch + yield resource_batch @ocean.router.post("/events") -async def handle_events(request: Request): +async def handle_events(cloud_event: CloudEvent): """ Handles System events from Azure Event Grid by the Azure subscription resource and registers them in Port The event payload is a CloudEvent https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema """ - cloud_evnet = await request.json() logger.debug( "Received azure cloud event", - event_id=cloud_evnet["id"], - event_type=cloud_evnet["type"], - resource_provider=cloud_evnet["data"]["resourceProvider"], - operation_name=cloud_evnet["data"]["operationName"], + event_id=cloud_event.id, + event_type=cloud_event.type, + resource_provider=cloud_event.data["resourceProvider"], + operation_name=cloud_event.data["operationName"], + ) + resource_type = resolve_resource_type_from_resource_uri( + cloud_event.data["resourceUri"] ) - resource_type = resolve_resource_type_from_cloud_event(cloud_evnet) if not resource_type: logger.warning( - "Weren't able to resolve resource type from cloud event", - resource_uri=cloud_evnet["data"]["resourceUri"], + "Could not resolve resource type from cloud event", + resource_uri=cloud_event.data["resourceUri"], ) - return {"ok": False} + return fastapi.Response(status_code=http.HTTPStatus.NOT_FOUND) resource_config = await get_port_resource_configuration_by_kind(resource_type) if not resource_config: @@ -89,31 +200,35 @@ async def handle_events(request: Request): "Resource type not found in port app config, update port app config to include the resource type", resource_type=resource_type, ) - return {"ok": False} + return fastapi.Response(status_code=http.HTTPStatus.NOT_FOUND) - async with DefaultAzureCredential() as credential: - async with ResourceManagementClient( - credential=credential, subscription_id=get_integration_subscription_id() - ) as client: + async with resource_client_context() as client: + logger.debug( + "Querying full resource", + id=cloud_event.data["resourceUri"], + kind=resource_type, + api_version=resource_config.selector.api_version, + ) + try: + resource = await client.resources.get_by_id( + resource_id=cloud_event.data["resourceUri"], + api_version=resource_config.selector.api_version, + ) + except ResourceNotFoundError: logger.debug( - "Querying full resource", - id=cloud_evnet["data"]["resourceUri"], + "Resource not found in azure, unregistering from port", + id=cloud_event.data["resourceUri"], kind=resource_type, - api_version=resource_config["selector"]["api_version"], + api_version=resource_config.selector.api_version, + ) + await ocean.unregister( + [ + Entity( + blueprint=resource_config.port.entity.mappings.blueprint, + identifier=cloud_event.data["resourceUri"], + ) + ] ) - try: - resource = await client.resources.get_by_id( - resource_id=cloud_evnet["data"]["resourceUri"], - api_version=resource_config["selector"]["api_version"], - ) - except ResourceNotFoundError: - # TODO: delete from port once ocean adds support to delete per identifier - logger.warning( - "Resource not found", - id=cloud_evnet["data"]["resourceUri"], - kind=resource_type, - api_version=resource_config["selector"]["api_version"], - ) - return {"ok": False} + return fastapi.Response(status_code=http.HTTPStatus.OK) await ocean.register_raw(resource_type, [resource.as_dict()]) - return {"ok": True} + return fastapi.Response(status_code=http.HTTPStatus.OK) diff --git a/integrations/azure/azure_integration/overrides.py b/integrations/azure/azure_integration/overrides.py index 7b04edcd69..31fecc9682 100644 --- a/integrations/azure/azure_integration/overrides.py +++ b/integrations/azure/azure_integration/overrides.py @@ -1,17 +1,17 @@ from port_ocean.core.handlers.port_app_config.models import ( - BaseModel, ResourceConfig, PortAppConfig, ) +from pydantic import BaseModel, Field class AzureResourceConfig(ResourceConfig): class Selector(BaseModel): query: str - api_version: str + api_version: str = Field(..., alias="apiVersion") - selector: Selector + selector: Selector # type: ignore class AzurePortAppConfig(PortAppConfig): - resources: list[AzureResourceConfig] = None + resources: list[AzureResourceConfig] = None # type: ignore diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index 2c92540b72..d2cbbc8cfa 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -1,50 +1,116 @@ +import contextlib +import enum import typing from port_ocean.context.event import event -from port_ocean.ocean import ocean +from port_ocean.context.ocean import ocean +from azure.identity.aio import DefaultAzureCredential +from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient + +from azure_integration.overrides import AzurePortAppConfig, AzureResourceConfig -from azure_integration.overrides import AzurePortAppConfig BATCH_SIZE = 20 +class ResourceKindsWithSpecialHandling(enum.StrEnum): + """ + Resource kinds with special handling + These resource kinds are handled separately from the other resource kinds + """ + + RESOURCE_GROUPS = "Microsoft.Resources/resourceGroups" + + def get_integration_subscription_id() -> str: logic_settings = ocean.integration_config - subscription_id = logic_settings.get("subscription_id", "") + subscription_id = logic_settings["subscription_id"] return subscription_id -async def get_port_resource_configuration_by_kind(kind: str) -> dict: +async def get_port_resource_configuration_by_kind( + kind: str, +) -> AzureResourceConfig | None: app_config = typing.cast(AzurePortAppConfig, event.port_app_config) for resource in app_config.resources: if resource.kind == kind: - return resource.dict() - return {} + return resource + return None -def resolve_resource_type_from_cloud_event(cloud_event: dict) -> str: +def resolve_resource_type_from_resource_uri(resource_uri: str) -> str: """ - Resolves the resource type from the cloud event payload + Resolves the resource type from azure resource uri - example of resource_uri in the event payload: + example of resource_uri: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/myVM pattern: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} - :param cloud_event: Cloud event payload + :param resource_uri: Azure resource uri :return: Resource type """ - resource_uri = cloud_event["data"]["resourceUri"] resource = resource_uri.split("/") if len(resource) < 8: - return "" - resource_type = f"{resource[6]}/{resource[7]}" + # Assuming that it is a resource group and not a resource + # example: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup + return ResourceKindsWithSpecialHandling.RESOURCE_GROUPS + + elif len(resource) == 8: + # Assuming that it is a resource + resource_type = "/".join(resource[6:]) + else: + # Assuming that it is an extension resource (e.g Microsoft.Storage/storageAccounts/blobServices/containers) + # For that we need to remove the parent resources names from the resource uri to construct the resource type + # example: + # resource_uri = /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/myStorageAccount/blobServices/default/containers/myContainer + # resource_type = Microsoft.Storage/storageAccounts/blobServices/containers + resource_type = "/".join(resource[6:8]) + # start from the first extension resource kind + for resource_kind_extension in range(len(resource[9:])): + # we want to skip the resource name and only add the extension resource kinds + if resource_kind_extension % 2 == 0: + resource_type += "/" + resource[9:][resource_kind_extension] + return resource_type +def is_sub_resource(resource_type: str) -> bool: + """ + Checks if the resource type is a sub resource + + Microsoft.Sql/servers/databases is a sub resource of Microsoft.Sql/servers + Microsoft.Sql/servers is not a sub resource + + :param resource_type: Resource type + :return: True if the resource type is a sub resource, False otherwise + """ + return len(resource_type.split("/")) > 2 + + +def get_resource_kind_by_level( + resource_kind: str, level: int = 0 +) -> typing.Tuple[str, bool]: + """ + Returns the resource kind by level and a boolean indicating if the resource kind is the last level + + example of resource_kind: + Microsoft.Storage/storageAccounts/blobServices/containers + + level 0: Microsoft.Storage/storageAccounts + level 1: Microsoft.Storage/storageAccounts/blobServices + level 2: Microsoft.Storage/storageAccounts/blobServices/containers + """ + base_level_index = 2 + resource_kind_list = resource_kind.split("/") + is_last_level = len(resource_kind_list) == base_level_index + level + return "/".join(resource_kind_list[: base_level_index + level]), is_last_level + + async def batch_resources_iterator( - async_list_method: typing.Callable[..., typing.AsyncIterable], **kwargs -) -> typing.AsyncIterable: + async_list_method: typing.Callable[..., typing.AsyncIterable[typing.Any]], + **kwargs: typing.Any, +) -> typing.AsyncIterable[typing.List[typing.Any]]: """ Iterates over the list method of the resources client and yields a list of resources. @@ -62,3 +128,16 @@ async def batch_resources_iterator( counter = 0 resource_list = [] yield resource_list + + +@contextlib.asynccontextmanager +async def resource_client_context() -> typing.AsyncIterator[ResourceManagementClient]: + """ + Creates a resource client context manager that yields a resource client with the default azure credentials + """ + async with DefaultAzureCredential() as credential: + async with ResourceManagementClient( + credential=credential, + subscription_id=get_integration_subscription_id(), + ) as client: + yield client diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml index fae42f4d43..21fbb928a6 100644 --- a/integrations/azure/config.yaml +++ b/integrations/azure/config.yaml @@ -12,7 +12,6 @@ integration: # The identifier of this integration instance. identifier: "{{ from env INTEGRATION_IDENTIFIER }}" # The type of the integration. - type: "Azure Integration" + type: "azure" config: - subscriptionId: "{{ from env AZURE_SUBSCRIPTION_ID }}" - someApplicationUrl: "https://I-Am-Not-A-Real-Url.com" \ No newline at end of file + subscriptionId: "{{ from env AZURE_SUBSCRIPTION_ID }}" \ No newline at end of file diff --git a/integrations/azure/debug.py b/integrations/azure/debug.py index 40b79f2d1c..2cdd7cd924 100644 --- a/integrations/azure/debug.py +++ b/integrations/azure/debug.py @@ -1,4 +1,145 @@ from port_ocean import run +from azure.identity.aio import DefaultAzureCredential +from azure.storage.queue import QueueClient +from azure.identity import DefaultAzureCredential as DefaultAzureCredentialSync +from azure.core.exceptions import ResourceNotFoundError +from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient +import asyncio +from typing import Any, Optional + +# import azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations +# from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient +# from azure.core.rest import HttpRequest +# from azure.mgmt.resource.resources.v2022_09_01.operations._operations import ( +# _format_url_section, +# _SERIALIZER, +# ) +# from loguru import logger +# +# +# def build_full_resources_list_request_patch( +# subscription_id: str, +# *, +# filter: Optional[str] = None, +# expand: Optional[str] = None, +# top: Optional[int] = None, +# **kwargs: Any, +# ) -> HttpRequest: +# """ +# Builds the request for the resources list request that will query the resource provider instead of the resources api +# +# The original request that is being built is: +# GET https://management.azure.com/subscriptions/{subscriptionId}/resources?api-version={apiVersion} +# The original request is querying the resources api which returns a list of resources with only the resource ID and +# resource type. +# +# The request that we want to build is: +# GET https://management.azure.com/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion} +# The request is querying the resource provider which returns a list of resources with all the properties. +# +# The resource type and api version are passed as headers to the request and are popped from the headers before the +# request is built. +# This is done because there is no way to pass the resource type and api version to the request builder due to the +# way the request builder is being called inside the list method. +# """ +# # Build the original request and the HttpRequest object +# request: HttpRequest = old_build_resources_list_request( +# subscription_id=subscription_id, filter=filter, expand=expand, top=top, **kwargs +# ) +# +# resource_type = request.headers.pop("resource-type", None) +# if resource_type: +# api_version = request.headers.pop("api-version", None) +# # Build the url +# url = "/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion}" +# path_format_arguments = { +# "subscriptionId": _SERIALIZER.url( +# "subscription_id", subscription_id, "str", min_length=1 +# ), +# "resourceType": _SERIALIZER.url("resource-type", resource_type, "str"), +# "apiVersion": _SERIALIZER.url("api-version", api_version, "str"), +# } +# # Format the url +# url = _format_url_section(url, **path_format_arguments) +# # Override the original url in the request +# request.url = url +# return request +# +# +# async def list_resources( +# resources_client: ResourceManagementClient, resource_type: str, api_version: str +# ): +# """ +# A list implementation that takes advantage of the patch implemented in this file. +# To be able to use this implementation, the resource type and api version must be passed as headers to the request. +# """ +# # override the default version in the client to the version that we want to query +# resources_client.resources._config.api_version = api_version +# async for resource in resources_client.resources.list( +# headers={"resource-type": resource_type, "api-version": api_version} +# ): +# logger.debug( +# "Found resource", +# resource_id=resource.id, +# kind=resource_type, +# api_version=api_version, +# ) +# yield resource +# +# +# # Patch the build_resources_list_request method in the resources operations module +# # This is done to be able to query the resource provider instead of the resources api +# # The original method is being called inside the list method in the resources client +# old_build_resources_list_request = ( +# azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request +# ) +# azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request = ( +# build_full_resources_list_request_patch +# ) +# +# +# async def main(): +# async with DefaultAzureCredential() as credential: +# async with ResourceManagementClient( +# credential=credential, +# subscription_id="7026e601-6a3b-4210-a9d6-b8742aa3b9f9", +# ) as client: +# async for resource in client.resources.list( +# filter="resourceType eq 'Microsoft.Sql/servers/databases'" +# ): +# resource = resource.as_dict() +# print(resource) +# resource_id = resource["id"].split("/") +# print(resource["type"], resource_id[5:-1], resource["id"]) +# resource_type = "Microsoft.Sql/servers/databases" +# resource_chief_type = resource_type.split("/")[:2] +# resource_sub_type = resource_type.split("/")[-1] +# async with DefaultAzureCredential() as credential: +# async with ResourceManagementClient( +# credential=credential, +# subscription_id="7026e601-6a3b-4210-a9d6-b8742aa3b9f9", +# ) as client: +# async for resource in client.resources.list( +# resource_type=resource_chief_type, +# api_version="2023-01-01", +# ): +# async for subset_resource in client.resources.list( +# resource_url=f"{resource.id}/{resource_sub_type}", +# api_version="2023-02-01-preview", +# ): +# print(subset_resource.as_dict()) +# # async for resource_group in client.resource_groups.list(): +# # async with ResourceManagementClient( +# # credential=credential, +# # subscription_id="7026e601-6a3b-4210-a9d6-b8742aa3b9f9", +# # ) as resource_mgmt_client: +# # async for resource_chief in resource_mgmt_client.resources.list( +# # resource_type=resource_chief_type, +# # api_version="2023-01-01", +# # ): +# # async for resource in +# if __name__ == "__main__": run() + # asyncio.run(main()) diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py index b822c4a4b9..21e52689c5 100644 --- a/integrations/azure/integration.py +++ b/integrations/azure/integration.py @@ -19,12 +19,13 @@ async def cloud_event_validation_middleware_handler( ) -> Response: """ Middleware used to handle cloud event validation requests + Azure topic subscription expects a 200 response with specific headers https://github.com/cloudevents/spec/blob/v1.0/http-webhook.md#42-validation-response """ if request.method == "OPTIONS" and request.url.path.startswith("/integration"): logger.info("Detected cloud event validation request") headers = { - "WebHook-Allowed-Rate": "1000", + "WebHook-Allowed-Rate": "100", "WebHook-Allowed-Origin": "*", } response = fastapi.Response(status_code=200, headers=headers) diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock index 4393a942ab..f8c1c68aa5 100644 --- a/integrations/azure/poetry.lock +++ b/integrations/azure/poetry.lock @@ -549,6 +549,23 @@ click = "*" [package.extras] test = ["pytest"] +[[package]] +name = "cloudevents" +version = "1.9.0" +description = "CloudEvents Python SDK" +optional = false +python-versions = "*" +files = [ + {file = "cloudevents-1.9.0-py3-none-any.whl", hash = "sha256:1011459d56d8f0184a46456f5d72632a2565f18171e51b33e06f643e723d30c9"}, + {file = "cloudevents-1.9.0.tar.gz", hash = "sha256:8beb27503f97e215f886f73c17671012e96bb6268137fb3b2f9ef552727ab5b1"}, +] + +[package.dependencies] +deprecation = ">=2.0,<3.0" + +[package.extras] +pydantic = ["pydantic (>=1.0.0,<2.0)"] + [[package]] name = "colorama" version = "0.4.6" @@ -672,6 +689,20 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "deprecation" +version = "2.1.0" +description = "A library to handle automated deprecations" +optional = false +python-versions = "*" +files = [ + {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, + {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, +] + +[package.dependencies] +packaging = "*" + [[package]] name = "dill" version = "0.3.7" @@ -2039,4 +2070,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "1d7fcec7db18cfa35334523237754b273ed96ca255c396e243018a690020144e" +content-hash = "0ca3f89fb5a6af1e74a64311749b4b501021b9cca3b8c91344055d9bd8f301dd" diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml index d9d3ce97f3..2c4f1fedcf 100644 --- a/integrations/azure/pyproject.toml +++ b/integrations/azure/pyproject.toml @@ -11,6 +11,8 @@ port_ocean = {version = "^0.2.1", extras = ["cli"]} azure-mgmt-resource = "23.0.1" azure-identity = "^1.13.0" aiohttp = "^3.7.4" +cloudevents = "^1.9.0" +requests = "^2.31.0" [tool.poetry.group.dev.dependencies] pytest = "^7.2" From 4004747974864c62cf58479ad6f57dee63bda278 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 08:01:48 +0300 Subject: [PATCH 68/95] separate pr --- .../core/event_listener/kafka/event_listener.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/port_ocean/core/event_listener/kafka/event_listener.py b/port_ocean/core/event_listener/kafka/event_listener.py index b3eee67b87..f1baa65fbd 100644 --- a/port_ocean/core/event_listener/kafka/event_listener.py +++ b/port_ocean/core/event_listener/kafka/event_listener.py @@ -52,13 +52,10 @@ async def _get_kafka_config(self) -> KafkaConsumerConfig: return KafkaConsumerConfig.parse_obj(self.event_listener_config.dict()) - def _should_be_processed(self, msg_value: dict[Any, Any], topic: str) -> bool: - after = msg_value.get("diff", {}).get("after", {}) - # handles delete events where there is no after - if after is None: - return False - - integration_identifier = after.get("identifier") + def should_be_processed(self, msg_value: dict[Any, Any], topic: str) -> bool: + integration_identifier = ( + msg_value.get("diff", {}).get("after", {}).get("identifier") + ) if integration_identifier == self.integration_identifier and ( "change.log" in topic ): @@ -67,7 +64,7 @@ def _should_be_processed(self, msg_value: dict[Any, Any], topic: str) -> bool: return False async def _handle_message(self, message: dict[Any, Any], topic: str) -> None: - if not self._should_be_processed(message, topic): + if not self.should_be_processed(message, topic): return if "change.log" in topic and message is not None: From c28d3a05e4401c38ba2c57ca409d9b4c3fcc5ac2 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 09:06:05 +0300 Subject: [PATCH 69/95] accepts variables as envs --- .../examples/azure-integration/main.tf | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf index f0339ad713..ef60cc1c84 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf @@ -1,10 +1,22 @@ +variable "port_client_id" { + type = string +} +variable "port_client_secret" { + type = string +} +variable "port_base_url" { + type = string + default = "" +} + module "ocean_integration" { source = "../.." # required port parameters so that the integration could communicate with Port port = { - client_id = "xxxxxxx" - client_secret = "xxxxxxx" + client_id = var.port_client_id + client_secret = var.port_client_secret + base_url = var.port_base_url } initialize_port_resources = true From 652fd8986b3d8374fb3355a620d297508138dc70 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 11:51:29 +0300 Subject: [PATCH 70/95] make the subscription creation more dynamic and add variables --- .../examples/azure-integration/main.tf | 58 +++++-------- .../examples/azure-integration/variables.tf | 85 +++++++++++++++++++ .../azure/container_app/variables.tf | 4 +- 3 files changed, 108 insertions(+), 39 deletions(-) create mode 100644 deployment/terraform/azure/container_app/examples/azure-integration/variables.tf diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf index ef60cc1c84..ab18692d3c 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf @@ -1,12 +1,9 @@ -variable "port_client_id" { - type = string -} -variable "port_client_secret" { - type = string -} -variable "port_base_url" { - type = string - default = "" +locals { + # splits the list into chunks of 25 elements, due to the limit of 25 elements in the advanced filtering for each subscription filter + # https://learn.microsoft.com/en-us/azure/event-grid/event-filtering#limitations + chunked_resources_filter_values = chunklist(var.resources_filter_values, 25) + # creates a dictionary with the index of the chunk as key and the chunk as value + chunked_resouces_filter_dict = { for i in range(length(local.chunked_resources_filter_values)) : i => local.chunked_resources_filter_values[i] } } module "ocean_integration" { @@ -19,44 +16,36 @@ module "ocean_integration" { base_url = var.port_base_url } - initialize_port_resources = true + initialize_port_resources = var.initialize_port_resources # required port integration parameters so Port could identify the integration integration = { type = "azure" - identifier = "az1" + identifier = var.integration_identifier config = { } } # optional port integration parameters - subscription_id = "/subscriptions/xxxxxx" - location = "East US 2" + subscription_id = "/subscriptions/${var.subscription_id}" + location = var.location - image = "ghcr.io/port-labs/port-ocean-azure:v0.1.0rc11" + image = var.image permissions = { - actions = [ - "microsoft.app/containerapps/read", - "Microsoft.Storage/storageAccounts/read", - "Microsoft.ContainerService/managedClusters/read", - "Microsoft.Network/loadBalancers/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Resources/subscriptions/resources/read", - ] + actions = var.action_permissions_list not_actions = [] data_actions = [] not_data_actions = [] } additional_secrets = { - OCEAN__INTEGRATION__CONFIG__SUBSCRIPTION_ID = "xxxxxxxxx" - } - additional_environment_variables = { - OCEAN__INTEGRATION__CONFIG__SOME_ENV_VAR = "some-value" + OCEAN__INTEGRATION__CONFIG__SUBSCRIPTION_ID = var.subscription_id } } resource "azurerm_eventgrid_system_topic" "subscription_event_grid_topic" { + # if the event grid topic name is not provided, the module will create a new one + count = var.event_grid_topic_name != "" ? 0 : 1 name = "subscription-event-grid-topic" resource_group_name = module.ocean_integration.resource_group_name location = "Global" @@ -66,9 +55,11 @@ resource "azurerm_eventgrid_system_topic" "subscription_event_grid_topic" { resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event_grid_topic_subscription" { - name = replace(replace("ocean-${module.ocean_integration.integration.type}-${module.ocean_integration.integration.identifier}-subscription","_", "-"),".","-") - resource_group_name = azurerm_eventgrid_system_topic.subscription_event_grid_topic.resource_group_name - system_topic = azurerm_eventgrid_system_topic.subscription_event_grid_topic.name + # creates a subscription for each chunk of filter values ( 25 per chunk ) + for_each = local.chunked_resouces_filter_dict + name = replace(replace("ocean-${module.ocean_integration.integration.type}-${module.ocean_integration.integration.identifier}-subscription-${each.key}","_", "-"),".","-") + resource_group_name = var.event_grid_resource_group != "" ? var.event_grid_resource_group: azurerm_eventgrid_system_topic.subscription_event_grid_topic[0].resource_group_name + system_topic = var.event_grid_topic_name != "" ? var.event_grid_topic_name : azurerm_eventgrid_system_topic.subscription_event_grid_topic[0].name included_event_types = [ "Microsoft.Resources.ResourceWriteSuccess", @@ -84,14 +75,7 @@ resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event advanced_filter { string_contains { key = "data.operationName" - values = [ - "microsoft.app/containerapps", - "Microsoft.Storage/storageAccounts", - "Microsoft.ContainerService/managedClusters", - "Microsoft.Network/loadBalancers", - "Microsoft.Compute/virtualMachine", - "Microsoft.Resources/subscriptions/resourceGroups", - ] + values = each.value } } delivery_property { diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf new file mode 100644 index 0000000000..02aa31ec40 --- /dev/null +++ b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf @@ -0,0 +1,85 @@ +variable "port_client_id" { + type = string +} +variable "port_client_secret" { + type = string +} +variable "port_base_url" { + type = string + default = "" +} +variable "event_grid_topic_name" { + type = string + default = "" + description = "If the event grid topic name is not provided, the module will create a new one" +} +variable "event_grid_resource_group" { + type = string + default = "" + description = "If the event grid resource group is not provided, the module will use the resource group of the event grid topic for the subscription event grid topic" +} +variable "resources_filter_values" { + type = list(string) + default = [ + "microsoft.app/containerapps", + "Microsoft.Storage/storageAccounts", + "Microsoft.ContainerService/managedClusters", + "Microsoft.Network/loadBalancers", + "Microsoft.Compute/virtualMachine", + "Microsoft.Resources/subscriptions/resourceGroups", + ] + description = "The list of resources that will be used for the advanced filtering of the subscription event grid topic" +} +variable "include_event_types" { + type = list(string) + default = [ + "Microsoft.Resources.ResourceWriteSuccess", + "Microsoft.Resources.ResourceWriteFailure", + "Microsoft.Resources.ResourceDeleteSuccess", + "Microsoft.Resources.ResourceDeleteFailure", + ] + description = "The list of event types to be included in the subscription event grid topic, for more information see https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema#available-event-types" +} + +variable "action_permissions_list" { + type = list(string) + default = [ + "microsoft.app/containerapps/read", + "Microsoft.Storage/storageAccounts/read", + "Microsoft.ContainerService/managedClusters/read", + "Microsoft.Network/loadBalancers/read", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Resources/subscriptions/resources/read", + ] + description = "The list of permissions that will be granted to the integration user" +} + +variable "image" { + type = string + default = "" + description = "The image that the integration will use, if not provided the module will use the latest image" +} + +variable "initialize_port_resources" { + type = bool + default = true + description = "If true, the module will initialize the port resources" +} + +variable "integration_identifier" { + type = string + default = "az" + description = "The identifier of the integration" +} + +variable "subscription_id" { + type = string + default = null + description = "The scope of the integration (e.g (00000000-0000-0000-0000-000000000000)" +} + +variable "location" { + type = string + default = "East US 2" + description = "The location to deploy the container to" +} diff --git a/deployment/terraform/azure/container_app/variables.tf b/deployment/terraform/azure/container_app/variables.tf index 906a625a34..07be134064 100644 --- a/deployment/terraform/azure/container_app/variables.tf +++ b/deployment/terraform/azure/container_app/variables.tf @@ -85,14 +85,14 @@ variable "image_registry" { variable "location" { type = string - default = "West US 2" + default = "East US 2" description = "The location to deploy the container to" } variable "subscription_id" { type = string default = null - description = "The scope of the user assigned identity and the scope of the role definition" + description = "The scope of the user assigned identity and the scope of the role definition (e.g /subscriptions/00000000-0000-0000-0000-000000000000)" } variable "resource_group_name" { From 1cf9a7ba9697638f715ce8a02593c9b460cfb5fe Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 11:56:00 +0300 Subject: [PATCH 71/95] minor --- .../container_app/examples/azure-integration/main.tf | 11 +++-------- .../examples/azure-integration/variables.tf | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf index ab18692d3c..b748202ef3 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf @@ -45,7 +45,7 @@ module "ocean_integration" { resource "azurerm_eventgrid_system_topic" "subscription_event_grid_topic" { # if the event grid topic name is not provided, the module will create a new one - count = var.event_grid_topic_name != "" ? 0 : 1 + count = var.event_grid_system_topic_name != "" ? 0 : 1 name = "subscription-event-grid-topic" resource_group_name = module.ocean_integration.resource_group_name location = "Global" @@ -59,14 +59,9 @@ resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event for_each = local.chunked_resouces_filter_dict name = replace(replace("ocean-${module.ocean_integration.integration.type}-${module.ocean_integration.integration.identifier}-subscription-${each.key}","_", "-"),".","-") resource_group_name = var.event_grid_resource_group != "" ? var.event_grid_resource_group: azurerm_eventgrid_system_topic.subscription_event_grid_topic[0].resource_group_name - system_topic = var.event_grid_topic_name != "" ? var.event_grid_topic_name : azurerm_eventgrid_system_topic.subscription_event_grid_topic[0].name + system_topic = var.event_grid_system_topic_name != "" ? var.event_grid_system_topic_name : azurerm_eventgrid_system_topic.subscription_event_grid_topic[0].name - included_event_types = [ - "Microsoft.Resources.ResourceWriteSuccess", - "Microsoft.Resources.ResourceWriteFailure", - "Microsoft.Resources.ResourceDeleteSuccess", - "Microsoft.Resources.ResourceDeleteFailure", - ] + included_event_types = var.included_event_types event_delivery_schema = "CloudEventSchemaV1_0" webhook_endpoint { url = "https://${module.ocean_integration.container_app_latest_fqdn}/integration/events" diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf index 02aa31ec40..190686d78d 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf @@ -8,7 +8,7 @@ variable "port_base_url" { type = string default = "" } -variable "event_grid_topic_name" { +variable "event_grid_system_topic_name" { type = string default = "" description = "If the event grid topic name is not provided, the module will create a new one" @@ -30,7 +30,7 @@ variable "resources_filter_values" { ] description = "The list of resources that will be used for the advanced filtering of the subscription event grid topic" } -variable "include_event_types" { +variable "included_event_types" { type = list(string) default = [ "Microsoft.Resources.ResourceWriteSuccess", From 9d64b42673afa99a9737b0767b4ffc2fe6854658 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 18:20:22 +0300 Subject: [PATCH 72/95] fix blueprints per morpaz CR --- .../azure/.port/resources/blueprints.json | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index c00049cc6c..32d3ba453c 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -34,7 +34,14 @@ }, "provisioningState": { "title": "Provisioning State", - "type": "string" + "type": "string", + "enum": [ + "Canceled", + "InProgress", + "Succeeded", + "Deleting", + "Failed" + ] }, "outboundIpAddresses": { "title": "Outbound IP Addresses", @@ -65,7 +72,7 @@ "resourceGroup": { "target": "resourceGroup", "title": "Resource Group", - "required": true, + "required": false, "many": false } } @@ -147,7 +154,12 @@ }, "skuTier": { "title": "Tier", - "type": "string" + "type": "string", + "enum": [ + "Free", + "Paid", + "Standard" + ] } }, "required": [] @@ -158,7 +170,7 @@ "resourceGroup": { "target": "resourceGroup", "title": "Resource Group", - "required": true, + "required": false, "many": false } } @@ -180,7 +192,13 @@ }, "provisioningState": { "title": "Provisioning State", - "type": "string" + "type": "string", + "enum": [ + "Succeeded", + "Updating", + "Deleting", + "Failed" + ] }, "frontendIpResourceIds": { "title": "Frontend IP Resource IDs", @@ -215,7 +233,7 @@ "resourceGroup": { "target": "resourceGroup", "title": "Resource Group", - "required": true, + "required": false, "many": false } } @@ -233,11 +251,17 @@ }, "provisioningState": { "title": "Provisioning State", - "type": "string" + "type": "string", + "enum": [ + "Creating", + "ResolvingDNS", + "Succeeded" + ] }, "creationTime": { "title": "Creation Time", - "type": "string" + "type": "string", + "format": "date-time" }, "isHnsEnabled": { "title": "Is HNS Enabled", @@ -301,7 +325,7 @@ "resourceGroup": { "target": "resourceGroup", "title": "Resource Group", - "required": true, + "required": false, "many": false } } @@ -362,7 +386,7 @@ "storageAccount": { "target": "storageAccount", "title": "Storage Account", - "required": true, + "required": false, "many": false } } @@ -439,7 +463,7 @@ "resourceGroup": { "target": "resourceGroup", "title": "Resource Group", - "required": true, + "required": false, "many": false } } From 5c006621e3f87a4d1d2e6eda127ee1550d5f26e6 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 19:05:56 +0300 Subject: [PATCH 73/95] fix linting --- integrations/azure/azure_integration/ocean.py | 39 ++++++++++--------- integrations/azure/azure_integration/utils.py | 8 ++++ integrations/azure/integration.py | 8 ++-- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index 54db8d5fd6..ffaed84aac 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -1,10 +1,11 @@ import http +import typing from cloudevents.pydantic import CloudEvent import fastapi from loguru import logger + from port_ocean.context.ocean import ocean -from port_ocean.context.event import event from port_ocean.core.models import Entity from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE from azure.identity.aio import DefaultAzureCredential @@ -21,6 +22,7 @@ batch_resources_iterator, is_sub_resource, get_resource_kind_by_level, + get_current_resource_config, ) from azure_integration.azure_patch import list_resources @@ -32,13 +34,12 @@ async def resync_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: if kind in iter(ResourceKindsWithSpecialHandling) or is_sub_resource(kind): logger.info("Kind is not a base resource, skipping resync", kind=kind) return - async with resource_client_context() as client: async for resources_batch in batch_resources_iterator( list_resources, resources_client=client, resource_type=kind, - api_version=event.resource_config.selector.api_version, + api_version=get_current_resource_config().selector.api_version, ): yield resources_batch @@ -56,7 +57,7 @@ async def resync_resource_groups(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: ) as client: async for resource_groups_batch in batch_resources_iterator( client.resource_groups.list, - api_version=event.resource_config.selector.api_version, + api_version=get_current_resource_config().selector.api_version, ): yield resource_groups_batch @@ -97,9 +98,10 @@ async def resync_extension_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: async with resource_client_context() as client: base_resource_kind, _ = get_resource_kind_by_level(kind, 0) + api_version = get_current_resource_config().selector.api_version async for resource in list_resources( client, - api_version=event.resource_config.selector.api_version, + api_version=api_version, resource_type=base_resource_kind, ): async for resource_batch in loop_over_extension_resource_kind( @@ -107,7 +109,7 @@ async def resync_extension_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: full_resource_kind=kind, kind_level=1, resource_id=resource.id, - api_version=event.resource_config.selector.api_version, + api_version=api_version, ): yield resource_batch @@ -118,7 +120,7 @@ async def loop_over_extension_resource_kind( kind_level: int, resource_id: str, api_version: str, -): +) -> typing.AsyncGenerator[typing.List[dict[str, typing.Any]], None]: """ Loops over a extension resource kind and yields a batch of resources @@ -170,27 +172,28 @@ async def loop_over_extension_resource_kind( @ocean.router.post("/events") -async def handle_events(cloud_event: CloudEvent): +async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: """ Handles System events from Azure Event Grid by the Azure subscription resource and registers them in Port The event payload is a CloudEvent https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema """ + cloud_event_data = typing.cast(cloud_event.data, dict[str, typing.Any]) # type: ignore logger.debug( "Received azure cloud event", event_id=cloud_event.id, event_type=cloud_event.type, - resource_provider=cloud_event.data["resourceProvider"], - operation_name=cloud_event.data["operationName"], + resource_provider=cloud_event_data["resourceProvider"], + operation_name=cloud_event_data["operationName"], ) resource_type = resolve_resource_type_from_resource_uri( - cloud_event.data["resourceUri"] + cloud_event_data["resourceUri"] ) if not resource_type: logger.warning( "Could not resolve resource type from cloud event", - resource_uri=cloud_event.data["resourceUri"], + resource_uri=cloud_event_data["resourceUri"], ) return fastapi.Response(status_code=http.HTTPStatus.NOT_FOUND) @@ -205,30 +208,30 @@ async def handle_events(cloud_event: CloudEvent): async with resource_client_context() as client: logger.debug( "Querying full resource", - id=cloud_event.data["resourceUri"], + id=cloud_event_data["resourceUri"], kind=resource_type, api_version=resource_config.selector.api_version, ) try: resource = await client.resources.get_by_id( - resource_id=cloud_event.data["resourceUri"], + resource_id=cloud_event_data["resourceUri"], api_version=resource_config.selector.api_version, ) except ResourceNotFoundError: logger.debug( "Resource not found in azure, unregistering from port", - id=cloud_event.data["resourceUri"], + id=cloud_event_data["resourceUri"], kind=resource_type, api_version=resource_config.selector.api_version, ) await ocean.unregister( [ Entity( - blueprint=resource_config.port.entity.mappings.blueprint, - identifier=cloud_event.data["resourceUri"], + blueprint=resource_config.port.entity.mappings.blueprint, # type: ignore + identifier=cloud_event_data["resourceUri"], ) ] ) return fastapi.Response(status_code=http.HTTPStatus.OK) - await ocean.register_raw(resource_type, [resource.as_dict()]) + await ocean.register_raw(resource_type, [resource.as_dict()]) # type: ignore return fastapi.Response(status_code=http.HTTPStatus.OK) diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index d2cbbc8cfa..d21477ab26 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -38,6 +38,14 @@ async def get_port_resource_configuration_by_kind( return None +def get_current_resource_config() -> AzureResourceConfig: + """ + Returns the current resource config, accessible only inside an event context + """ + # for some reason mypy doesn't recognize the `resource_config` as defined in the event context, ignoring it + return typing.cast(event.resource_config, AzureResourceConfig) # type: ignore + + def resolve_resource_type_from_resource_uri(resource_uri: str) -> str: """ Resolves the resource type from azure resource uri diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py index 21e52689c5..c243fea913 100644 --- a/integrations/azure/integration.py +++ b/integrations/azure/integration.py @@ -1,4 +1,5 @@ -from requests import Request, Response +from requests import Request +from starlette import responses from typing import Awaitable, Callable import fastapi @@ -15,8 +16,9 @@ class AppConfigHandlerClass(APIPortAppConfig): async def cloud_event_validation_middleware_handler( - request: Request, call_next: Callable[[Request], Awaitable[Response]] -) -> Response: + request: Request, + call_next: Callable[[Request], Awaitable[responses.Response]], +) -> responses.Response: """ Middleware used to handle cloud event validation requests Azure topic subscription expects a 200 response with specific headers From e11f6329358560425ded459e9da734f5b168b3a9 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 20:07:05 +0300 Subject: [PATCH 74/95] add virtualMachine permission --- .../container_app/examples/azure-integration/variables.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf index 190686d78d..1f56355374 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf @@ -44,12 +44,13 @@ variable "included_event_types" { variable "action_permissions_list" { type = list(string) default = [ + "Microsoft.Resources/subscriptions/resources/read", "microsoft.app/containerapps/read", "Microsoft.Storage/storageAccounts/read", "Microsoft.ContainerService/managedClusters/read", "Microsoft.Network/loadBalancers/read", "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Resources/subscriptions/resources/read", + "Microsoft.Compute/virtualMachine/read", ] description = "The list of permissions that will be granted to the integration user" } From 5e92a687cbededa99d8b9d8302a9f4e8a7202fde Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 20:07:19 +0300 Subject: [PATCH 75/95] spaces --- integrations/azure/.port/resources/port-app-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index 70706d1455..e25db07923 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -155,7 +155,6 @@ resources: # resolve storage account id from container id storageAccount: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:-4] |join("/")' - - kind: Microsoft.Compute/virtualMachines selector: query: "true" @@ -185,4 +184,4 @@ resources: tags: .tags relations: # resolve resource group id from virtual machine id - resourceGroup: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:5] |join("/")' \ No newline at end of file + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:5] |join("/")' From 7429db3d95237643ea5fa5c2c89e87d7cd1c2f4a Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 20:07:28 +0300 Subject: [PATCH 76/95] add README.md --- integrations/azure/README.md | 901 ++++++++++++++++++++++++++++++++++- 1 file changed, 899 insertions(+), 2 deletions(-) diff --git a/integrations/azure/README.md b/integrations/azure/README.md index 9ad4e2c263..83a4629b06 100644 --- a/integrations/azure/README.md +++ b/integrations/azure/README.md @@ -1,19 +1,916 @@ # Azure -Azure integration +Integration to import azure resources into Port. ## Development Requirements - Python3.11.0 - Poetry (Python Package Manager) - Port-Ocean +- azure-mgmt-resource +- azure-identity +- requests +- aiohttp +- cloudevents + + +## Pre-Requisites + +Before using this module, make sure you have completed the following prerequisites: +- Install the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and login to your Azure account. + ```bash + az login + ``` +- Export your [Port Credentials](https://docs.getport.io/build-your-software-catalog/sync-data-to-catalog/api/#find-your-port-credentials). + ```bash + export TF_VAR_port_client_id= + export TF_VAR_port_client_secret= + ``` + +### Azure + +Before deploying the integration you'll need to consider and check the following prerequisites: + +#### Azure Authorization +The integration will need to have permissions to read the resources you want to import from Azure to Port. + +- **Azure Role Definition**: Permissions are granted to the integration using an Azure Role Definition. The integration will create a new Role Definition with the required permissions. + - **Permissions** - The integration will need to have the following permissions: (An example will be provided later on in the documentation) + - **Actions**: You'll need to specify the [actions](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-definitions#actions) you want to grant the integration. + +#### Azure Infrastructure + +- **Azure Subscription**: You'll need an Azure subscription to deploy the integration. +- **Azure Resource Group**: The integration need to be deployed to an Azure Resource Group, you can pass an existing one, or the integration will create a new one. (To override add `resource_group_id: `) +- **Azure Container App**: The integration will be deployed using Azure Container App. Which requires some extra infrastructure to be deployed, by default if not specified otherwise we will deploy the associate infrastructure. + - **Azure Log Analytics Workspace**: The integration will create a new Log Analytics Workspace to store the logs of the integration. (To override add `log_analytics_workspace_id: `) + - **Azure Container App Environment**: The integration will create a new Container App Environment to deploy the integration to. (To override add `container_app_environment_id: `) + +#### Azure Event Grid +- **Azure Event Grid System Topic**: To allow the integration to receive events from Azure, an Event Grid System Topic of type `Microsoft.Resources.Subscriptions` will be needed. The integration will create a new System Topic and will subscribe to it. (Due to a limitation in Azure only one Event Grid System Topic of type `Microsoft.Resources.Subscriptions` can be created per subscription, so if you already have one you'll need to pass it to the integration using `event_grid_system_topic_name: `) Further example on how to create the System Topic will be provided later on in the documentation. +- **Azure Event Grid Subscription**: To Pass the events from the System Topic to the integration, an Event Grid Subscription will be needed. The integration will create a new Subscription and will pass the events to the integration. + ## Installation +The integration is deployed using Terraform on Azure [ContainerApp](https://learn.microsoft.com/en-us/azure/container-apps/overview). + +The integration is being triggered in two ways: +- On events sends from the Azure Event Grid. +- When a change in the integration configuration is detected. + +```sh +export TF_VAR_port_client_id= +export TF_VAR_port_client_secret= + +git clone git@github.com:port-labs/Port-Ocean.git + +cd deployment/azure/container_app/examples + +terraform init + +terraform apply -var="subscription_id=" +``` + +## Supported Kinds + + +### Resource Group + +The mapping should refer to one of the resource groups from the example response: [Azure documentation](https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/list) + +
+blueprints.json + +```json + { + "identifier": "resourceGroup", + "description": "This blueprint represents an Azure Resource Group in our software catalog", + "title": "Resource Group", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioningState": { + "title": "Provisioning State", + "type": "string" + }, + "tags": { + "title": "Tags", + "type": "object" + } + } + } + } +``` +
+ + +
+port-app-config.yaml + +```yaml + - kind: Microsoft.Resources/resourceGroups + selector: + query: "true" + # azure resource api version to query + apiVersion: '2022-09-01' + port: + entity: + mappings: + # lower case the resource group namespace and name to align with other azure resources + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"resourceGroup"' + properties: + location: .location + # the provisioning state property is returned in lower case when using the SDK and in camelCase when using the REST API + # therefore supporting both (for users who use the SDK) + provisioningState: .properties.provisioningState + .properties.provisioning_state + tags: .tags +``` + +
+ +### Container App + +The mapping should refer to one of the container apps from the example response: [Azure documentation](https://docs.microsoft.com/en-us/rest/api/container-apps/containerapps/list) + +
+blueprints.json + +```json + { + "identifier": "containerApp", + "description": "This blueprint represents an Azure Container App in our software catalog", + "title": "Container App", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioningState": { + "title": "Provisioning State", + "type": "string", + "enum": [ + "Canceled", + "InProgress", + "Succeeded", + "Deleting", + "Failed" + ] + }, + "outboundIpAddresses": { + "title": "Outbound IP Addresses", + "type": "array" + }, + "externalIngress": { + "title": "External Ingress", + "type": "boolean" + }, + "hostName": { + "title": "Host Name", + "type": "string" + }, + "minReplicas": { + "title": "Min Replicas", + "type": "number" + }, + "maxReplicas": { + "title": "Max Replicas", + "type": "number" + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", + "required": false, + "many": false + } + } + } +``` + +
+ +
+port-app-config.yaml + +```yaml + - kind: Microsoft.App/containerApps + selector: + query: "true" + # azure resource api version to query + apiVersion: '2022-03-01' + port: + entity: + mappings: + # lower case the resource group namespace and name to align with other azure resources + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"containerApp"' + properties: + location: .location + provisioningState: .properties.provisioningState + outboundIpAddresses: .properties.outboundIpAddresses + externalIngress: .properties.configuration.ingress.external + hostName: .properties.configuration.ingress.fqdn + minReplicas: .properties.template.scale.minReplicas + maxReplicas: .properties.template.scale.maxReplicas + relations: + # lower case the resource group namespace and name to align with other azure resources + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' +``` +
+ +### AKS + +The mapping should refer to one of the AKS from the example response: [Azure documentation](https://docs.microsoft.com/en-us/rest/api/aks/managedclusters/list) + +
+blueprints.json + +```json + { + "identifier": "aks", + "description": "This blueprint represents an Azure Kubernetes Service in our software catalog", + "title": "AKS", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioningState": { + "title": "Provisioning State", + "type": "string" + }, + "powerState": { + "title": "Power State", + "type": "string" + }, + "kubernetesVersion": { + "title": "Kubernetes Version", + "type": "string" + }, + "currentKubernetesVersion": { + "title": "Current Kubernetes Version", + "type": "string" + }, + "dnsPrefix": { + "title": "DNS Prefix", + "type": "string" + }, + "fqdn": { + "title": "FQDN", + "type": "string" + }, + "nodeResourceGroup": { + "title": "Node Resource Group", + "type": "string" + }, + "enableRBAC": { + "title": "Enable RBAC", + "type": "boolean" + }, + "supportPlan": { + "title": "Support Plan", + "type": "string" + }, + "networkPlugin": { + "title": "Network Plugin", + "type": "string" + }, + "podCIDR": { + "title": "Pod CIDR", + "type": "string" + }, + "serviceCIDR": { + "title": "Service CIDR", + "type": "string" + }, + "dnsServiceIp": { + "title": "DNS Service IP", + "type": "string" + }, + "outboundType": { + "title": "Outbound Type", + "type": "string" + }, + "loadBalancerSKU": { + "title": "Load Balancer SKU", + "type": "string" + }, + "maxAgentPools": { + "title": "Max Agent Pools", + "type": "number" + }, + "skuTier": { + "title": "Tier", + "type": "string", + "enum": [ + "Free", + "Paid", + "Standard" + ] + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", + "required": false, + "many": false + } + } + } +``` + +
+ +
+port-app-config.yaml + +```yaml + port: + entity: + mappings: + # lower case the resource group namespace and name to align with other azure resources + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"aks"' + properties: + location: .location + provisioningState: .properties.provisioningState + powerState: .properties.powerState.code + kubernetesVersion: .properties.kubernetesVersion + currentKubernetesVersion: .properties.currentKubernetesVersion + dnsPrefix: .properties.dnsPrefix + fqdn: .properties.fqdn + nodeResourceGroup: .properties.nodeResourceGroup + enableRBAC: .properties.enableRBAC + supportPlan: .properties.supportPlan + networkPlugin: .properties.networkProfile.networkPlugin + podCIDR: .properties.networkProfile.podCidr + serviceCIDR: .properties.networkProfile.serviceCidr + dnsServiceIp: .properties.networkProfile.dnsServiceIP + outboundType: .properties.networkProfile.outboundType + loadBalancerSKU: .properties.networkProfile.loadBalancerSku + maxAgentPools: .properties.maxAgentPools + skuTier: .properties.sku.tier + relations: + # lower case the resource group namespace and name to align with other azure resources + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' +``` + +
+ +### Load Balancer + +The mapping should refer to one of the load balancers from the example response: [Azure documentation](https://docs.microsoft.com/en-us/rest/api/network/loadbalancers/list) + +
+blueprints.json + +```json + { + "identifier": "loadBalancer", + "description": "This blueprint represents an Azure Load Balancer in our software catalog", + "title": "Load Balancer", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "tags": { + "title": "Tags", + "type": "object" + }, + "provisioningState": { + "title": "Provisioning State", + "type": "string", + "enum": [ + "Succeeded", + "Updating", + "Deleting", + "Failed" + ] + }, + "frontendIpResourceIds": { + "title": "Frontend IP Resource IDs", + "type": "array" + }, + "backendAddressPoolsResourceIds": { + "title": "Backend Address Pools Resource IDs", + "type": "array" + }, + "loadBalancingRulesResourceIds": { + "title": "Load Balancing Rules Resource IDs", + "type": "array" + }, + "probesResourceIds": { + "title": "Probes Resource IDs", + "type": "array" + }, + "inboundNatRulesResourceIds": { + "title": "Inbound NAT Rules Resource IDs", + "type": "array" + }, + "inboundNatPoolsResourceIds": { + "title": "Inbound NAT Pools Resource IDs", + "type": "array" + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", + "required": false, + "many": false + } + } + } +``` + +
+ +
+port-app-config.yaml + +```yaml + - kind: Microsoft.Network/loadBalancers + selector: + query: "true" + # azure resource api version to query + apiVersion: '2023-02-01' + port: + entity: + mappings: + # lower case the resource group namespace and name to align with other azure resources + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"loadBalancer"' + properties: + location: .location + provisioningState: .properties.provisioningState + tags: .tags + frontendIpResourceIds: .properties.frontendIPConfigurations[].id + backendAddressPoolResourceIds: .properties.backendAddressPools[].id + loadBalancingRulesResourceIds: .properties.loadBalancingRules[].id + probesResourceIds: .properties.probes[].id + inboundNatRulesResourceIds: .properties.inboundNatRules[].id + inboundNatPoolsResourceIds: .properties.inboundNatPools[].id + relations: + # lower case the resource group namespace and name to align with other azure resources + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' +``` + +
+ +### Virtual Machine + +The mapping should refer to one of the virtual machines from the example response: [Azure documentation](https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines/list) + +
+blueprints.json + +```json + { + "identifier": "virtualMachine", + "description": "This blueprint represents an Azure Virtual Machine in our software catalog", + "title": "Virtual Machine", + "icon": "Azure", + "schema": { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioningState": { + "title": "Provisioning State", + "type": "string" + }, + "vmSize": { + "title": "VM Size", + "type": "string" + }, + "osDiskName": { + "title": "OS Disk Name", + "type": "string" + }, + "osDiskType": { + "title": "OS Disk Type", + "type": "string" + }, + "osDiskCaching": { + "title": "OS Disk Caching", + "type": "string" + }, + "osDiskSizeGB": { + "title": "OS Disk Size GB", + "type": "number" + }, + "osDiskCreateOption": { + "title": "OS Disk Create Option", + "type": "string" + }, + "networkInterfaceIds": { + "title": "Network Interface IDs", + "type": "array" + }, + "licenseType": { + "title": "License Type", + "type": "string" + }, + "vmOsProfile": { + "title": "VM OS Profile", + "type": "object" + }, + "vmHardwareProfile": { + "title": "VM Hardware Profile", + "type": "object" + }, + "vmStorageProfile": { + "title": "VM Storage Profile", + "type": "object" + }, + "tags": { + "title": "Tags", + "type": "object" + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", + "required": false, + "many": false + } + } + } +``` + +
+ +
+port-app-config.yaml + +```yaml + - kind: Microsoft.Compute/virtualMachines + selector: + query: "true" + # azure resource api version to query + apiVersion: '2023-03-01' + port: + entity: + mappings: + # lower case the resource group namespace and name to align with other azure resources + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"virtualMachine"' + properties: + location: .location + provisioningState: .properties.provisioningState + vmSize: .properties.hardwareProfile.vmSize + osDiskName: .properties.storageProfile.osDisk.name + osType: .properties.storageProfile.osDisk.osType + osDiskCaching: .properties.storageProfile.osDisk.caching + osDiskSizeGB: .properties.storageProfile.osDisk.diskSizeGB + osDiskCreateOption: .properties.storageProfile.osDisk.createOption + networkInterfaceIds: .properties.networkProfile.networkInterfaces[].id + licenseType: .properties.licenseType + vmOsProfile: .properties.osProfile + vmHardwareProfile: .properties.hardwareProfile + vmStorageProfile: .properties.storageProfile + tags: .tags + relations: + # lower case the resource group namespace and name to align with other azure resources + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:5] |join("/")' +``` + +
+ + +### Storage Account + +The mapping should refer to one of the storage accounts from the example response: [Azure documentation](https://docs.microsoft.com/en-us/rest/api/storagerp/storageaccounts/list) + +
+blueprints.json + +```json + { + "identifier": "storageAccount", + "description": "This blueprint represents an Azure Storage Account in our software catalog", + "title": "Storage Account", + "icon": "Azure", + "schema" : { + "properties": { + "location": { + "title": "Location", + "type": "string" + }, + "provisioningState": { + "title": "Provisioning State", + "type": "string", + "enum": [ + "Creating", + "ResolvingDNS", + "Succeeded" + ] + }, + "creationTime": { + "title": "Creation Time", + "type": "string", + "format": "date-time" + }, + "isHnsEnabled": { + "title": "Is HNS Enabled", + "type": "boolean", + "default": false + }, + "fileEncryptionEnabled": { + "title": "File Encryption Enabled", + "type": "boolean" + }, + "blobEncryptionEnabled": { + "title": "Blob Encryption Enabled", + "type": "boolean" + }, + "primaryLocation": { + "title": "Primary Location", + "type": "string" + }, + "secondaryLocation": { + "title": "Secondary Location", + "type": "string" + }, + "statusOfPrimary": { + "title": "Status of Primary", + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "enumColors": { + "unavailable": "red", + "available": "green" + } + }, + "statusOfSecondary": { + "title": "Status of Secondary", + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "enumColors": { + "unavailable": "red", + "available": "green" + } + }, + "tags": { + "title": "Tags", + "type": "object" + }, + "allowBlobPublicAccess": { + "title": "Allow Blob Public Access", + "type": "boolean" + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "resourceGroup": { + "target": "resourceGroup", + "title": "Resource Group", + "required": false, + "many": false + } + } + } +``` + +
+ +
+port-app-config.yaml + +```yaml + - kind: Microsoft.Storage/storageAccounts + selector: + query: "true" + # azure resource api version to query + apiVersion: '2023-01-01' + port: + entity: + mappings: + # lower case the resource group namespace and name to align with other azure resources + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"storageAccount"' + properties: + location: .location + provisioningState: .properties.provisioningState + creationTime: .properties.creationTime + isHnsEnabled: .properties.isHnsEnabled + fileEncryptionEnabled: .properties.encryption.services.file.enabled + blobEncryptionEnabled: .properties.encryption.services.blob.enabled + primaryLocation: .properties.primaryLocation + secondaryLocation: .properties.secondaryLocation + statusOfPrimary: .properties.statusOfPrimary + statusOfSecondary: .properties.statusOfSecondary + allowBlobPublicAccess: .properties.allowBlobPublicAccess + tags: .tags + relations: + # lower case the resource group namespace and name to align with other azure resources + resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' +``` + +
+ + +### Storage Container (Blob) + +The mapping should refer to one of the storage containers from the example response: [Azure documentation](https://learn.microsoft.com/en-us/rest/api/storagerp/blob-containers/list) + +
+blueprints.json + +```json + { + "identifier": "storageContainer", + "description": "This blueprint represents an Azure Storage Container in our software catalog", + "title": "Storage Container", + "icon": "S3", + "schema": { + "properties": { + "publicAccess": { + "title": "Public Access", + "type": "string" + }, + "hasImmutabilityPolicy": { + "title": "Has Immutability Policy", + "type": "boolean" + }, + "hasLegalHold": { + "title": "Has Legal Hold", + "type": "boolean" + }, + "deleted": { + "title": "Deleted", + "type": "boolean" + }, + "deletedTime": { + "title": "Deleted Time", + "type": "string" + }, + "remainingRetentionDays": { + "title": "Remaining Retention Days", + "type": "number" + }, + "leaseStatus": { + "title": "Lease Status", + "type": "string" + }, + "leaseState": { + "title": "Lease State", + "type": "string" + }, + "defaultEncryptionScope": { + "title": "Default Encryption Scope", + "type": "string" + }, + "version": { + "title": "Version", + "type": "string" + } + }, + "required": [] + }, + "mirrorProperties": {}, + "calculationProperties": {}, + "relations": { + "storageAccount": { + "target": "storageAccount", + "title": "Storage Account", + "required": false, + "many": false + } + } + } +``` + +
+ +
+port-app-config.yaml + +```yaml + - kind: Microsoft.Storage/storageAccounts/blobServices/containers + selector: + query: "true" + # azure resource api version to query + apiVersion: '2023-01-01' + port: + entity: + mappings: + # lower case the resource group namespace and name to align with other azure resources + identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' + title: .name + blueprint: '"storageContainer"' + properties: + publicAccess: .properties.publicAccess + hasImmutabilityPolicy: .properties.hasImmutabilityPolicy + hasLegalHold: .properties.hasLegalHold + deleted: .properties.deleted + deletedTime: .properties.deletedTime + remainingRetentionDays: .properties.remainingRetentionDays + leaseStatus: .properties.leaseStatus + leaseState: .properties.leaseState + defaultEncryptionScope: .properties.defaultEncryptionScope + version: .properties.version + relations: + # lower case the resource group namespace and name to align with other azure resources + storageAccount: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:-4] |join("/")' +``` + +
+ + +## Adding a new Azure Resource kind + +### Blueprints +To add a new Azure Resource kind, you'll need to add a new blueprint to the `blueprints.json` file. + +### Mapping +To add a new Azure Resource kind, you'll need to add a new mapping to the `port-app-config.yaml` file. +- The mapping **must** contain an `apiVersion` field under the `selector` section. The `apiVersion` is a required field when querying Azure resources. To find the correct `apiVersion` for the resource you want to query, you can use the [Azure REST API](https://docs.microsoft.com/en-us/rest/api/azure/) documentation to find the resource you want to query. +- The mapping **must** lower case the resource group namespace and name in the identifier as well to any other relation, so it will be able to relate to the resource group blueprint. (Example: `resourceGroups` -> `resourcegroups`) This is due to the fact that each azure API returns the resource group namespace and name in a different format, and we want to align them all to the same format. here is an example on how to apply it: `.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")`. + +### Authorization +When adding new Azure resource we need to make sure our exporter has permissions to query it. +To do so we will need to add a new action permission to the integration role definition. +- The action permission should be added to the `actions` section of the integration role definition. + +### Event Grid +When adding new Azure resource we need to make sure our exporter will be able to receive events related to it. +To do so we will need to add a new event filter to the integration event grid subscription. For more information on [action permissions](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-definitions#actions) + +### Example +Here is an example on how to apply it, as in the base example we will head to the `deployment/azure/container_app/examples` directory +and apply the terraform changes. + +`Microsoft.Network/virtualNetworks` is the resource we want to add. ```sh -make install +export TF_VAR_port_client_id= +export TF_VAR_port_client_secret= + +git clone git@github.com:port-labs/Port-Ocean.git + +cd deployment/azure/container_app/examples + +terraform init + +terraform apply -var='subscription_id=' -var='action_permissions_list=["Microsoft.app/containerapps/read","Microsoft.Storage/storageAccounts/read","Microsoft.ContainerService/managedClusters/read","Microsoft.Network/loadBalancers/read","Microsoft.Resources/subscriptions/resourceGroups/read","Microsoft.Resources/subscriptions/resources/read", "Microsoft.Network/virtualNetworks/read"]' -var='event_grid_event_filter_list=["Microsoft.App/containerApp","Microsoft.Storage/storageAccounts","Microsoft.Compute/virtualMachines","Microsoft.Network/loadBalancers","Microsoft.Resources/subscriptions/resourceGroups", "Microsoft.Network/virtualNetworks]' ``` +Let's go over the changes we made: +- We added a new action permission to the integration role definition: `"microsoft.network/virtualnetworks/read"`. +- We added a new event filter to the integration event grid subscription: `"Microsoft.Network/virtualNetworks"` this will check if the operation name received from the event grid contains `"Microsoft.Network/virtualNetworks"`. + +Note that currently the terraform module doesn't support just appending to the default lists, to allow overriding the default list, so we need to pass the full list of action permissions and event filters. + +The apiVersion we want to query of `Microsoft.Network/virtualNetworks` is `2023-02-01` which we can find in the [Azure REST API](https://learn.microsoft.com/en-us/rest/api/virtualnetwork/virtual-networks/list) + + ## Runnning Localhost ```sh make run From f27dae04a5b92dadade6611c36aafe3d16638faf Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 20:09:27 +0300 Subject: [PATCH 77/95] adjust resources --- integrations/azure/.port/spec.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 067ba5fbf9..db79f22f9b 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -6,9 +6,12 @@ features: - type: exporter section: Cloud Providers resources: - - kind: resource_group - - kind: container_app + - kind: resourceGroup + - kind: containerApp - kind: aks + - kind: storageAccount + - kind: virtualMachine + - kind: storageContainer configurations: - name: subscriptionId required: true From fdaa82e6a8869fc1f5e21516e55f77f11a65b027 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 20:10:51 +0300 Subject: [PATCH 78/95] remove --- integrations/azure/config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/integrations/azure/config.yaml b/integrations/azure/config.yaml index 21fbb928a6..1c45c46aa9 100644 --- a/integrations/azure/config.yaml +++ b/integrations/azure/config.yaml @@ -4,7 +4,6 @@ initializePortResources: true port: clientId: "{{ from env PORT_CLIENT_ID }}" # Can be loaded via environment variable: PORT_CLIENT_ID, if both are set, the environment variable will be used. clientSecret: "{{ from env PORT_CLIENT_SECRET }}" # Can be loaded via environment variable: PORT_CLIENT_SECRET, if both are set, the environment variable will be used. - baseUrl: "{{ from env PORT_BASE_URL }}" # The event listener to use for the integration service. eventListener: type: POLLING From a86ee4fb5f2973c4be47ae891070712c1a9d8161 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 20:13:34 +0300 Subject: [PATCH 79/95] add changelog --- integrations/azure/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/integrations/azure/CHANGELOG.md b/integrations/azure/CHANGELOG.md index 14e279d66d..8648526b0a 100644 --- a/integrations/azure/CHANGELOG.md +++ b/integrations/azure/CHANGELOG.md @@ -6,3 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# 0.1.0 (2023-08-13) + +### Features + +- Add Azure ocean integration [PORT-4351] (#0) From e25a7ab66ff45d02c0e7ab21c51f67758453642f Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Sun, 13 Aug 2023 20:14:52 +0300 Subject: [PATCH 80/95] remove redundant --- port_ocean/ocean.py | 1 - 1 file changed, 1 deletion(-) diff --git a/port_ocean/ocean.py b/port_ocean/ocean.py index 30463b91ee..20f6c1b622 100644 --- a/port_ocean/ocean.py +++ b/port_ocean/ocean.py @@ -2,7 +2,6 @@ from typing import Callable from fastapi import FastAPI, APIRouter -from fastapi.middleware.cors import CORSMiddleware from loguru import logger from pydantic import BaseModel from starlette.types import Scope, Receive, Send From 1ebc497a02e46a1b0b1008b51e6b798d740898b1 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 11:20:49 +0300 Subject: [PATCH 81/95] bump rc13 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index db79f22f9b..4a509649cd 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc12 +version: v0.1.0rc13 type: azure description: azure integration for Port Ocean icon: Azure From 92af7ea00c08bfef2d7a785e03afee024837588c Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 11:26:01 +0300 Subject: [PATCH 82/95] oops --- integrations/azure/debug.py | 141 ------------------------------------ 1 file changed, 141 deletions(-) diff --git a/integrations/azure/debug.py b/integrations/azure/debug.py index 2cdd7cd924..40b79f2d1c 100644 --- a/integrations/azure/debug.py +++ b/integrations/azure/debug.py @@ -1,145 +1,4 @@ from port_ocean import run -from azure.identity.aio import DefaultAzureCredential -from azure.storage.queue import QueueClient -from azure.identity import DefaultAzureCredential as DefaultAzureCredentialSync -from azure.core.exceptions import ResourceNotFoundError -from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient -import asyncio -from typing import Any, Optional - -# import azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations -# from azure.mgmt.resource.resources.v2022_09_01.aio import ResourceManagementClient -# from azure.core.rest import HttpRequest -# from azure.mgmt.resource.resources.v2022_09_01.operations._operations import ( -# _format_url_section, -# _SERIALIZER, -# ) -# from loguru import logger -# -# -# def build_full_resources_list_request_patch( -# subscription_id: str, -# *, -# filter: Optional[str] = None, -# expand: Optional[str] = None, -# top: Optional[int] = None, -# **kwargs: Any, -# ) -> HttpRequest: -# """ -# Builds the request for the resources list request that will query the resource provider instead of the resources api -# -# The original request that is being built is: -# GET https://management.azure.com/subscriptions/{subscriptionId}/resources?api-version={apiVersion} -# The original request is querying the resources api which returns a list of resources with only the resource ID and -# resource type. -# -# The request that we want to build is: -# GET https://management.azure.com/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion} -# The request is querying the resource provider which returns a list of resources with all the properties. -# -# The resource type and api version are passed as headers to the request and are popped from the headers before the -# request is built. -# This is done because there is no way to pass the resource type and api version to the request builder due to the -# way the request builder is being called inside the list method. -# """ -# # Build the original request and the HttpRequest object -# request: HttpRequest = old_build_resources_list_request( -# subscription_id=subscription_id, filter=filter, expand=expand, top=top, **kwargs -# ) -# -# resource_type = request.headers.pop("resource-type", None) -# if resource_type: -# api_version = request.headers.pop("api-version", None) -# # Build the url -# url = "/subscriptions/{subscriptionId}/providers/{resourceType}?api-version={apiVersion}" -# path_format_arguments = { -# "subscriptionId": _SERIALIZER.url( -# "subscription_id", subscription_id, "str", min_length=1 -# ), -# "resourceType": _SERIALIZER.url("resource-type", resource_type, "str"), -# "apiVersion": _SERIALIZER.url("api-version", api_version, "str"), -# } -# # Format the url -# url = _format_url_section(url, **path_format_arguments) -# # Override the original url in the request -# request.url = url -# return request -# -# -# async def list_resources( -# resources_client: ResourceManagementClient, resource_type: str, api_version: str -# ): -# """ -# A list implementation that takes advantage of the patch implemented in this file. -# To be able to use this implementation, the resource type and api version must be passed as headers to the request. -# """ -# # override the default version in the client to the version that we want to query -# resources_client.resources._config.api_version = api_version -# async for resource in resources_client.resources.list( -# headers={"resource-type": resource_type, "api-version": api_version} -# ): -# logger.debug( -# "Found resource", -# resource_id=resource.id, -# kind=resource_type, -# api_version=api_version, -# ) -# yield resource -# -# -# # Patch the build_resources_list_request method in the resources operations module -# # This is done to be able to query the resource provider instead of the resources api -# # The original method is being called inside the list method in the resources client -# old_build_resources_list_request = ( -# azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request -# ) -# azure.mgmt.resource.resources.v2022_09_01.aio.operations._operations.build_resources_list_request = ( -# build_full_resources_list_request_patch -# ) -# -# -# async def main(): -# async with DefaultAzureCredential() as credential: -# async with ResourceManagementClient( -# credential=credential, -# subscription_id="7026e601-6a3b-4210-a9d6-b8742aa3b9f9", -# ) as client: -# async for resource in client.resources.list( -# filter="resourceType eq 'Microsoft.Sql/servers/databases'" -# ): -# resource = resource.as_dict() -# print(resource) -# resource_id = resource["id"].split("/") -# print(resource["type"], resource_id[5:-1], resource["id"]) -# resource_type = "Microsoft.Sql/servers/databases" -# resource_chief_type = resource_type.split("/")[:2] -# resource_sub_type = resource_type.split("/")[-1] -# async with DefaultAzureCredential() as credential: -# async with ResourceManagementClient( -# credential=credential, -# subscription_id="7026e601-6a3b-4210-a9d6-b8742aa3b9f9", -# ) as client: -# async for resource in client.resources.list( -# resource_type=resource_chief_type, -# api_version="2023-01-01", -# ): -# async for subset_resource in client.resources.list( -# resource_url=f"{resource.id}/{resource_sub_type}", -# api_version="2023-02-01-preview", -# ): -# print(subset_resource.as_dict()) -# # async for resource_group in client.resource_groups.list(): -# # async with ResourceManagementClient( -# # credential=credential, -# # subscription_id="7026e601-6a3b-4210-a9d6-b8742aa3b9f9", -# # ) as resource_mgmt_client: -# # async for resource_chief in resource_mgmt_client.resources.list( -# # resource_type=resource_chief_type, -# # api_version="2023-01-01", -# # ): -# # async for resource in -# if __name__ == "__main__": run() - # asyncio.run(main()) From 6944128dbea55a759b89c3b7a9ce8c27b3119887 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 13:43:23 +0300 Subject: [PATCH 83/95] fix virtualMachines --- .../azure/container_app/examples/azure-integration/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf index 1f56355374..1a0fb3aba8 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf @@ -50,7 +50,7 @@ variable "action_permissions_list" { "Microsoft.ContainerService/managedClusters/read", "Microsoft.Network/loadBalancers/read", "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Compute/virtualMachine/read", + "Microsoft.Compute/virtualMachines/read", ] description = "The list of permissions that will be granted to the integration user" } From a8050ef4447194cd7ce79da6a8b94e3546f16c2e Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 15:21:56 +0300 Subject: [PATCH 84/95] fix cast --- integrations/azure/azure_integration/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/azure_integration/utils.py b/integrations/azure/azure_integration/utils.py index d21477ab26..70e82f21f9 100644 --- a/integrations/azure/azure_integration/utils.py +++ b/integrations/azure/azure_integration/utils.py @@ -43,7 +43,7 @@ def get_current_resource_config() -> AzureResourceConfig: Returns the current resource config, accessible only inside an event context """ # for some reason mypy doesn't recognize the `resource_config` as defined in the event context, ignoring it - return typing.cast(event.resource_config, AzureResourceConfig) # type: ignore + return event.resource_config # type: ignore def resolve_resource_type_from_resource_uri(resource_uri: str) -> str: From 111db1cf5b2de89f10080f04ef7f3fca46e15907 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 15:22:12 +0300 Subject: [PATCH 85/95] bump rc --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 4a509649cd..2b72e4c527 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc13 +version: v0.1.0rc14 type: azure description: azure integration for Port Ocean icon: Azure From a5a629f9dd02386db65aef719fc1d484b329ffd0 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 18:06:00 +0300 Subject: [PATCH 86/95] fix cr comments --- .../examples/azure-integration/variables.tf | 10 ++++----- .../modules/container_app/variables.tf | 2 +- .../azure/container_app/variables.tf | 2 +- integrations/azure/.port/spec.yaml | 6 +++-- integrations/azure/CHANGELOG.md | 2 +- integrations/azure/Dockerfile | 11 +++++----- integrations/azure/Makefile | 6 ++++- integrations/azure/README.md | 22 +++++++++++-------- integrations/azure/azure_integration/ocean.py | 6 ++--- integrations/azure/integration.py | 2 +- integrations/azure/pyproject.toml | 1 - 11 files changed, 39 insertions(+), 31 deletions(-) diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf index 1a0fb3aba8..e8d24610df 100644 --- a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf +++ b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf @@ -22,13 +22,13 @@ variable "resources_filter_values" { type = list(string) default = [ "microsoft.app/containerapps", - "Microsoft.Storage/storageAccounts", "Microsoft.ContainerService/managedClusters", "Microsoft.Network/loadBalancers", "Microsoft.Compute/virtualMachine", "Microsoft.Resources/subscriptions/resourceGroups", + "Microsoft.Storage/storageAccounts", ] - description = "The list of resources that will be used for the advanced filtering of the subscription event grid topic" + description = "The list of resources that integration will receive events for" } variable "included_event_types" { type = list(string) @@ -64,12 +64,12 @@ variable "image" { variable "initialize_port_resources" { type = bool default = true - description = "If true, the module will initialize the port resources" + description = "If true, the module will initialize the default port resources (blueprints and relations)" } variable "integration_identifier" { type = string - default = "az" + default = "azure" description = "The identifier of the integration" } @@ -81,6 +81,6 @@ variable "subscription_id" { variable "location" { type = string - default = "East US 2" + default = "East US" description = "The location to deploy the container to" } diff --git a/deployment/terraform/azure/container_app/modules/container_app/variables.tf b/deployment/terraform/azure/container_app/modules/container_app/variables.tf index b51bc93031..085bf890fd 100644 --- a/deployment/terraform/azure/container_app/modules/container_app/variables.tf +++ b/deployment/terraform/azure/container_app/modules/container_app/variables.tf @@ -27,7 +27,7 @@ variable "event_listener" { variable "additional_secrets" { type = map(string) default = {} - description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name" + description = 'Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: "my-secret"}' } variable "additional_environment_variables" { diff --git a/deployment/terraform/azure/container_app/variables.tf b/deployment/terraform/azure/container_app/variables.tf index 07be134064..b5f8fbff65 100644 --- a/deployment/terraform/azure/container_app/variables.tf +++ b/deployment/terraform/azure/container_app/variables.tf @@ -27,7 +27,7 @@ variable "event_listener" { variable "additional_secrets" { type = map(string) default = {} - description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name" + description = 'Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: "my-secret"}' } variable "additional_environment_variables" { diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 2b72e4c527..a78202081d 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc14 +version: v0.1.0rc15 type: azure description: azure integration for Port Ocean icon: Azure @@ -16,4 +16,6 @@ configurations: - name: subscriptionId required: true type: string - sensitive: true \ No newline at end of file + sensitive: true +deploymentMethod: + deployments: \ No newline at end of file diff --git a/integrations/azure/CHANGELOG.md b/integrations/azure/CHANGELOG.md index 8648526b0a..85adc20e8c 100644 --- a/integrations/azure/CHANGELOG.md +++ b/integrations/azure/CHANGELOG.md @@ -11,4 +11,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features -- Add Azure ocean integration [PORT-4351] (#0) +- Added Azure ocean integration [PORT-4351] (#0) diff --git a/integrations/azure/Dockerfile b/integrations/azure/Dockerfile index cdb8b20ce9..1436f8b004 100644 --- a/integrations/azure/Dockerfile +++ b/integrations/azure/Dockerfile @@ -4,13 +4,12 @@ ENV LIBRDKAFKA_VERSION 1.9.2 WORKDIR /app -RUN apt update && apt install -y wget make g++ libssl-dev autoconf automake libtool curl -RUN wget https://github.com/edenhill/librdkafka/archive/v${LIBRDKAFKA_VERSION}.tar.gz && \ - tar xvzf v${LIBRDKAFKA_VERSION}.tar.gz && \ - (cd librdkafka-${LIBRDKAFKA_VERSION}/ && ./configure && make && make install && ldconfig) +RUN apt update && \ + apt install -y wget make g++ libssl-dev autoconf automake libtool curl librdkafka-dev && \ + apt-get clean COPY . /app -RUN make install +RUN export POETRY_VIRTUALENVS_CREATE=false && make install/prod && pip cache purge -ENTRYPOINT make run \ No newline at end of file +ENTRYPOINT ocean sail \ No newline at end of file diff --git a/integrations/azure/Makefile b/integrations/azure/Makefile index 399de69631..9a6067dc1f 100644 --- a/integrations/azure/Makefile +++ b/integrations/azure/Makefile @@ -38,13 +38,17 @@ define deactivate_virtualenv fi endef -.SILENT: install lint run test clean +.SILENT: install install/prod lint run test clean install: $(call deactivate_virtualenv) && \ $(call install_poetry) && \ poetry install --with dev +install/prod: + $(call install_poetry) && \ + poetry install --without dev --no-root --no-interaction --no-ansi --no-cache + lint: $(ACTIVATE) && \ $(call run_checks,.) diff --git a/integrations/azure/README.md b/integrations/azure/README.md index 83a4629b06..00a036941b 100644 --- a/integrations/azure/README.md +++ b/integrations/azure/README.md @@ -41,13 +41,13 @@ The integration will need to have permissions to read the resources you want to #### Azure Infrastructure - **Azure Subscription**: You'll need an Azure subscription to deploy the integration. -- **Azure Resource Group**: The integration need to be deployed to an Azure Resource Group, you can pass an existing one, or the integration will create a new one. (To override add `resource_group_id: `) -- **Azure Container App**: The integration will be deployed using Azure Container App. Which requires some extra infrastructure to be deployed, by default if not specified otherwise we will deploy the associate infrastructure. - - **Azure Log Analytics Workspace**: The integration will create a new Log Analytics Workspace to store the logs of the integration. (To override add `log_analytics_workspace_id: `) - - **Azure Container App Environment**: The integration will create a new Container App Environment to deploy the integration to. (To override add `container_app_environment_id: `) +- **Azure Resource Group**: The integration need to be deployed to an Azure Resource Group, you can pass an existing one, or the integration will create a new one. (To override add `-var='resource_group_id='`) +- **Azure Container App**: The integration will be deployed using Azure Container App. Which requires some extra infrastructure to be deployed, by default if not specified otherwise we will deploy the required infrastructure. + - **Azure Log Analytics Workspace**: The integration will create a new Log Analytics Workspace to store the logs of the integration. (To override add `-var='log_analytics_workspace_id='` to the terraform apply command) + - **Azure Container App Environment**: The integration will create a new Container App Environment to deploy the integration to. (To override add `-var='container_app_environment_id='` to the terraform apply command) #### Azure Event Grid -- **Azure Event Grid System Topic**: To allow the integration to receive events from Azure, an Event Grid System Topic of type `Microsoft.Resources.Subscriptions` will be needed. The integration will create a new System Topic and will subscribe to it. (Due to a limitation in Azure only one Event Grid System Topic of type `Microsoft.Resources.Subscriptions` can be created per subscription, so if you already have one you'll need to pass it to the integration using `event_grid_system_topic_name: `) Further example on how to create the System Topic will be provided later on in the documentation. +- **Azure Event Grid System Topic**: To allow the integration to receive events from Azure, an Event Grid System Topic of type `Microsoft.Resources.Subscriptions` will be needed. The integration will create a new System Topic and will subscribe to it. (Due to a limitation in Azure only one Event Grid System Topic of type `Microsoft.Resources.Subscriptions` can be created per subscription, so if you already have one you'll need to pass it to the integration using `-var='event_grid_system_topic_name='`) Further example on how to create the System Topic will be provided later on in the documentation. - **Azure Event Grid Subscription**: To Pass the events from the System Topic to the integration, an Event Grid Subscription will be needed. The integration will create a new Subscription and will pass the events to the integration. @@ -55,10 +55,12 @@ The integration will need to have permissions to read the resources you want to The integration is deployed using Terraform on Azure [ContainerApp](https://learn.microsoft.com/en-us/azure/container-apps/overview). -The integration is being triggered in two ways: -- On events sends from the Azure Event Grid. +The integration can be triggered in two ways: +- On events sent from the Azure Event Grid. - When a change in the integration configuration is detected. +To deploy the integration, run the following commands: + ```sh export TF_VAR_port_client_id= export TF_VAR_port_client_secret= @@ -69,7 +71,7 @@ cd deployment/azure/container_app/examples terraform init -terraform apply -var="subscription_id=" +terraform apply -var='subscription_id=' ``` ## Supported Kinds @@ -867,6 +869,8 @@ The mapping should refer to one of the storage containers from the example respo ## Adding a new Azure Resource kind +Adding new azure resource that the integration will know how to handle requires the following steps: + ### Blueprints To add a new Azure Resource kind, you'll need to add a new blueprint to the `blueprints.json` file. @@ -899,7 +903,7 @@ cd deployment/azure/container_app/examples terraform init -terraform apply -var='subscription_id=' -var='action_permissions_list=["Microsoft.app/containerapps/read","Microsoft.Storage/storageAccounts/read","Microsoft.ContainerService/managedClusters/read","Microsoft.Network/loadBalancers/read","Microsoft.Resources/subscriptions/resourceGroups/read","Microsoft.Resources/subscriptions/resources/read", "Microsoft.Network/virtualNetworks/read"]' -var='event_grid_event_filter_list=["Microsoft.App/containerApp","Microsoft.Storage/storageAccounts","Microsoft.Compute/virtualMachines","Microsoft.Network/loadBalancers","Microsoft.Resources/subscriptions/resourceGroups", "Microsoft.Network/virtualNetworks]' +terraform apply -var='action_permissions_list=["Microsoft.Network/virtualNetworks/read", ]' -var='event_grid_event_filter_list=["Microsoft.Network/virtualNetworks",]' -var='subscription_id=' ``` Let's go over the changes we made: diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index ffaed84aac..967f883172 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -69,12 +69,12 @@ async def resync_extension_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: The Resource Management API does not support listing extension resources The only way to list extension resources is to query the base resource and get the extension resources from the response - This method take advantage of the fact that the resource id is the same as the resource url, - and uses it to query the base resource and with like that get the extension resources + This method takes advantage of the fact that the resource id is the same as the resource url, + and uses it to query the base resource which it uses to get the extension resources We support multiple levels of extension resources For example: - Microsoft.Storage/storageAccounts/blobServices/containers is an extension resource, and also it parent + Microsoft.Storage/storageAccounts/blobServices/containers is an extension resource, and its parent Microsoft.Storage/storageAccounts/blobServices is an extension resource of Microsoft.Storage/storageAccounts Microsoft.Storage/storageAccounts is a base resource diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py index c243fea913..fcc941fa05 100644 --- a/integrations/azure/integration.py +++ b/integrations/azure/integration.py @@ -1,4 +1,4 @@ -from requests import Request +from requests import Request # type: ignore from starlette import responses from typing import Awaitable, Callable diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml index 2c4f1fedcf..ccd30616f0 100644 --- a/integrations/azure/pyproject.toml +++ b/integrations/azure/pyproject.toml @@ -25,7 +25,6 @@ towncrier = "^23.6.0" [tool.towncrier] directory = "changelog" filename = "CHANGELOG.md" -package = "port_ocean" [[tool.towncrier.type]] directory = "breaking" From 061e7a1e933b5f53fdac3176ed6ee3c3a83ba250 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 18:08:33 +0300 Subject: [PATCH 87/95] minor --- .../azure/container_app/modules/container_app/variables.tf | 2 +- deployment/terraform/azure/container_app/variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/terraform/azure/container_app/modules/container_app/variables.tf b/deployment/terraform/azure/container_app/modules/container_app/variables.tf index 085bf890fd..4812a87553 100644 --- a/deployment/terraform/azure/container_app/modules/container_app/variables.tf +++ b/deployment/terraform/azure/container_app/modules/container_app/variables.tf @@ -27,7 +27,7 @@ variable "event_listener" { variable "additional_secrets" { type = map(string) default = {} - description = 'Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: "my-secret"}' + description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: \"my-secret\"}" } variable "additional_environment_variables" { diff --git a/deployment/terraform/azure/container_app/variables.tf b/deployment/terraform/azure/container_app/variables.tf index b5f8fbff65..5fa52f8067 100644 --- a/deployment/terraform/azure/container_app/variables.tf +++ b/deployment/terraform/azure/container_app/variables.tf @@ -27,7 +27,7 @@ variable "event_listener" { variable "additional_secrets" { type = map(string) default = {} - description = 'Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: "my-secret"}' + description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: \"my-secret\"}" } variable "additional_environment_variables" { From d39880e5599287e430010dbf88c6af25692ac5eb Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Mon, 14 Aug 2023 18:32:07 +0300 Subject: [PATCH 88/95] minor --- integrations/azure/.port/spec.yaml | 2 +- integrations/azure/azure_integration/ocean.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index a78202081d..31989cf529 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc15 +version: v0.1.0rc16 type: azure description: azure integration for Port Ocean icon: Azure diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index 967f883172..cfebd9cf0c 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -179,21 +179,21 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema """ - cloud_event_data = typing.cast(cloud_event.data, dict[str, typing.Any]) # type: ignore logger.debug( "Received azure cloud event", event_id=cloud_event.id, event_type=cloud_event.type, - resource_provider=cloud_event_data["resourceProvider"], - operation_name=cloud_event_data["operationName"], + resource_provider=getattr(cloud_event.data, "resourceProvider"), + operation_name=getattr(cloud_event.data, "operationName"), ) + resource_uri = getattr(cloud_event.data, "resourceUri") resource_type = resolve_resource_type_from_resource_uri( - cloud_event_data["resourceUri"] + resource_uri=resource_uri, ) if not resource_type: logger.warning( "Could not resolve resource type from cloud event", - resource_uri=cloud_event_data["resourceUri"], + resource_uri=resource_uri, ) return fastapi.Response(status_code=http.HTTPStatus.NOT_FOUND) @@ -208,19 +208,19 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: async with resource_client_context() as client: logger.debug( "Querying full resource", - id=cloud_event_data["resourceUri"], + id=resource_uri, kind=resource_type, api_version=resource_config.selector.api_version, ) try: resource = await client.resources.get_by_id( - resource_id=cloud_event_data["resourceUri"], + resource_id=resource_uri, api_version=resource_config.selector.api_version, ) except ResourceNotFoundError: logger.debug( "Resource not found in azure, unregistering from port", - id=cloud_event_data["resourceUri"], + id=resource_uri, kind=resource_type, api_version=resource_config.selector.api_version, ) @@ -228,7 +228,7 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: [ Entity( blueprint=resource_config.port.entity.mappings.blueprint, # type: ignore - identifier=cloud_event_data["resourceUri"], + identifier=resource_uri, ) ] ) From 1bb39a7dbae987f0b4bbe36f0854d918b15c7608 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Tue, 15 Aug 2023 10:49:39 +0300 Subject: [PATCH 89/95] bump rc17 and minor fixes --- integrations/azure/.port/resources/blueprints.json | 2 +- integrations/azure/.port/resources/port-app-config.yaml | 2 +- integrations/azure/.port/spec.yaml | 2 +- integrations/azure/azure_integration/ocean.py | 7 ++++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/integrations/azure/.port/resources/blueprints.json b/integrations/azure/.port/resources/blueprints.json index 32d3ba453c..84f28c14de 100644 --- a/integrations/azure/.port/resources/blueprints.json +++ b/integrations/azure/.port/resources/blueprints.json @@ -334,7 +334,7 @@ "identifier": "storageContainer", "description": "This blueprint represents an Azure Storage Container in our software catalog", "title": "Storage Container", - "icon": "S3", + "icon": "Azure", "schema": { "properties": { "publicAccess": { diff --git a/integrations/azure/.port/resources/port-app-config.yaml b/integrations/azure/.port/resources/port-app-config.yaml index e25db07923..8f75644fc2 100644 --- a/integrations/azure/.port/resources/port-app-config.yaml +++ b/integrations/azure/.port/resources/port-app-config.yaml @@ -153,7 +153,7 @@ resources: version: .properties.version relations: # resolve storage account id from container id - storageAccount: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:-4] |join("/")' + storageAccount: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:-4] | join("/")' - kind: Microsoft.Compute/virtualMachines selector: diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 31989cf529..e6ae12f5d1 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc16 +version: v0.1.0rc17 type: azure description: azure integration for Port Ocean icon: Azure diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index cfebd9cf0c..32593263df 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -179,14 +179,15 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema """ + cloud_event_data: dict[str, typing.Any] = cloud_event.data # type: ignore logger.debug( "Received azure cloud event", event_id=cloud_event.id, event_type=cloud_event.type, - resource_provider=getattr(cloud_event.data, "resourceProvider"), - operation_name=getattr(cloud_event.data, "operationName"), + resource_provider=cloud_event_data["resourceProvider"], + operation_name=cloud_event_data["operationName"], ) - resource_uri = getattr(cloud_event.data, "resourceUri") + resource_uri = cloud_event_data["resourceUri"] resource_type = resolve_resource_type_from_resource_uri( resource_uri=resource_uri, ) From ce679c44d403c0d3da71a517fecd5c1cb76a48b2 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Tue, 15 Aug 2023 14:47:14 +0300 Subject: [PATCH 90/95] fix --- integrations/azure/azure_integration/ocean.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index 32593263df..b6ff334067 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -180,7 +180,7 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: https://learn.microsoft.com/en-us/azure/event-grid/cloud-event-schema """ cloud_event_data: dict[str, typing.Any] = cloud_event.data # type: ignore - logger.debug( + logger.info( "Received azure cloud event", event_id=cloud_event.id, event_type=cloud_event.type, @@ -219,7 +219,7 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: api_version=resource_config.selector.api_version, ) except ResourceNotFoundError: - logger.debug( + logger.info( "Resource not found in azure, unregistering from port", id=resource_uri, kind=resource_type, @@ -228,7 +228,9 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: await ocean.unregister( [ Entity( - blueprint=resource_config.port.entity.mappings.blueprint, # type: ignore + # remove the quotes from the blueprint name + # TODO: remove this once the port client handles it + blueprint=resource_config.port.entity.mappings.blueprint.strip('"'), # type: ignore identifier=resource_uri, ) ] From fa3b5e6e32604bf24910cdd9c4a90cf2c2e6dd87 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Tue, 15 Aug 2023 14:47:30 +0300 Subject: [PATCH 91/95] rc18 --- integrations/azure/.port/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index e6ae12f5d1..9be3e1d277 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc17 +version: v0.1.0rc18 type: azure description: azure integration for Port Ocean icon: Azure From a36172c81a585aaa621b7820498e8d12b5947c16 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 17 Aug 2023 13:15:46 +0300 Subject: [PATCH 92/95] bump to release --- .../examples/azure-integration/main.tf | 86 ---------- .../examples/azure-integration/providers.tf | 19 --- .../examples/azure-integration/variables.tf | 86 ---------- .../terraform/azure/container_app/main.tf | 36 ---- .../modules/container_app/main.tf | 110 ------------ .../modules/container_app/outputs.tf | 11 -- .../modules/container_app/variables.tf | 150 ---------------- .../terraform/azure/container_app/outputs.tf | 27 --- .../azure/container_app/providers.tf | 27 --- .../azure/container_app/variables.tf | 160 ------------------ .../azure/modules/authorization/main.tf | 31 ---- .../azure/modules/authorization/outputs.tf | 11 -- .../azure/modules/authorization/variables.tf | 48 ------ integrations/azure/.port/spec.yaml | 7 +- integrations/azure/README.md | 79 ++++++--- 15 files changed, 58 insertions(+), 830 deletions(-) delete mode 100644 deployment/terraform/azure/container_app/examples/azure-integration/main.tf delete mode 100644 deployment/terraform/azure/container_app/examples/azure-integration/providers.tf delete mode 100644 deployment/terraform/azure/container_app/examples/azure-integration/variables.tf delete mode 100644 deployment/terraform/azure/container_app/main.tf delete mode 100644 deployment/terraform/azure/container_app/modules/container_app/main.tf delete mode 100644 deployment/terraform/azure/container_app/modules/container_app/outputs.tf delete mode 100644 deployment/terraform/azure/container_app/modules/container_app/variables.tf delete mode 100644 deployment/terraform/azure/container_app/outputs.tf delete mode 100644 deployment/terraform/azure/container_app/providers.tf delete mode 100644 deployment/terraform/azure/container_app/variables.tf delete mode 100644 deployment/terraform/azure/modules/authorization/main.tf delete mode 100644 deployment/terraform/azure/modules/authorization/outputs.tf delete mode 100644 deployment/terraform/azure/modules/authorization/variables.tf diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf deleted file mode 100644 index b748202ef3..0000000000 --- a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf +++ /dev/null @@ -1,86 +0,0 @@ -locals { - # splits the list into chunks of 25 elements, due to the limit of 25 elements in the advanced filtering for each subscription filter - # https://learn.microsoft.com/en-us/azure/event-grid/event-filtering#limitations - chunked_resources_filter_values = chunklist(var.resources_filter_values, 25) - # creates a dictionary with the index of the chunk as key and the chunk as value - chunked_resouces_filter_dict = { for i in range(length(local.chunked_resources_filter_values)) : i => local.chunked_resources_filter_values[i] } -} - -module "ocean_integration" { - source = "../.." - - # required port parameters so that the integration could communicate with Port - port = { - client_id = var.port_client_id - client_secret = var.port_client_secret - base_url = var.port_base_url - } - - initialize_port_resources = var.initialize_port_resources - - # required port integration parameters so Port could identify the integration - integration = { - type = "azure" - identifier = var.integration_identifier - config = { - } - } - # optional port integration parameters - subscription_id = "/subscriptions/${var.subscription_id}" - location = var.location - - image = var.image - - permissions = { - actions = var.action_permissions_list - not_actions = [] - data_actions = [] - not_data_actions = [] - } - - additional_secrets = { - OCEAN__INTEGRATION__CONFIG__SUBSCRIPTION_ID = var.subscription_id - } -} - -resource "azurerm_eventgrid_system_topic" "subscription_event_grid_topic" { - # if the event grid topic name is not provided, the module will create a new one - count = var.event_grid_system_topic_name != "" ? 0 : 1 - name = "subscription-event-grid-topic" - resource_group_name = module.ocean_integration.resource_group_name - location = "Global" - topic_type = "Microsoft.Resources.Subscriptions" - source_arm_resource_id = module.ocean_integration.subscription_id -} - - -resource "azurerm_eventgrid_system_topic_event_subscription" "subscription_event_grid_topic_subscription" { - # creates a subscription for each chunk of filter values ( 25 per chunk ) - for_each = local.chunked_resouces_filter_dict - name = replace(replace("ocean-${module.ocean_integration.integration.type}-${module.ocean_integration.integration.identifier}-subscription-${each.key}","_", "-"),".","-") - resource_group_name = var.event_grid_resource_group != "" ? var.event_grid_resource_group: azurerm_eventgrid_system_topic.subscription_event_grid_topic[0].resource_group_name - system_topic = var.event_grid_system_topic_name != "" ? var.event_grid_system_topic_name : azurerm_eventgrid_system_topic.subscription_event_grid_topic[0].name - - included_event_types = var.included_event_types - event_delivery_schema = "CloudEventSchemaV1_0" - webhook_endpoint { - url = "https://${module.ocean_integration.container_app_latest_fqdn}/integration/events" - } - advanced_filtering_on_arrays_enabled = true - advanced_filter { - string_contains { - key = "data.operationName" - values = each.value - } - } - delivery_property { - header_name = "Access-Control-Request-Method" - type = "Static" - value = "POST" - } - delivery_property { - header_name = "Origin" - type = "Static" - value = "azure" - } -} diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/providers.tf b/deployment/terraform/azure/container_app/examples/azure-integration/providers.tf deleted file mode 100644 index 260f326f74..0000000000 --- a/deployment/terraform/azure/container_app/examples/azure-integration/providers.tf +++ /dev/null @@ -1,19 +0,0 @@ -terraform { - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "3.68.0" - } - } -} -provider "azurerm" { - # The AzureRM Provider supports authenticating using via the Azure CLI, a Managed Identity - # and a Service Principal. More information on the authentication methods supported by - # the AzureRM Provider can be found here: - # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure - - # The features block allows changing the behaviour of the Azure Provider, more - # information can be found here: - # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/features-block - features {} -} diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf b/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf deleted file mode 100644 index e8d24610df..0000000000 --- a/deployment/terraform/azure/container_app/examples/azure-integration/variables.tf +++ /dev/null @@ -1,86 +0,0 @@ -variable "port_client_id" { - type = string -} -variable "port_client_secret" { - type = string -} -variable "port_base_url" { - type = string - default = "" -} -variable "event_grid_system_topic_name" { - type = string - default = "" - description = "If the event grid topic name is not provided, the module will create a new one" -} -variable "event_grid_resource_group" { - type = string - default = "" - description = "If the event grid resource group is not provided, the module will use the resource group of the event grid topic for the subscription event grid topic" -} -variable "resources_filter_values" { - type = list(string) - default = [ - "microsoft.app/containerapps", - "Microsoft.ContainerService/managedClusters", - "Microsoft.Network/loadBalancers", - "Microsoft.Compute/virtualMachine", - "Microsoft.Resources/subscriptions/resourceGroups", - "Microsoft.Storage/storageAccounts", - ] - description = "The list of resources that integration will receive events for" -} -variable "included_event_types" { - type = list(string) - default = [ - "Microsoft.Resources.ResourceWriteSuccess", - "Microsoft.Resources.ResourceWriteFailure", - "Microsoft.Resources.ResourceDeleteSuccess", - "Microsoft.Resources.ResourceDeleteFailure", - ] - description = "The list of event types to be included in the subscription event grid topic, for more information see https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions?tabs=event-grid-event-schema#available-event-types" -} - -variable "action_permissions_list" { - type = list(string) - default = [ - "Microsoft.Resources/subscriptions/resources/read", - "microsoft.app/containerapps/read", - "Microsoft.Storage/storageAccounts/read", - "Microsoft.ContainerService/managedClusters/read", - "Microsoft.Network/loadBalancers/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Compute/virtualMachines/read", - ] - description = "The list of permissions that will be granted to the integration user" -} - -variable "image" { - type = string - default = "" - description = "The image that the integration will use, if not provided the module will use the latest image" -} - -variable "initialize_port_resources" { - type = bool - default = true - description = "If true, the module will initialize the default port resources (blueprints and relations)" -} - -variable "integration_identifier" { - type = string - default = "azure" - description = "The identifier of the integration" -} - -variable "subscription_id" { - type = string - default = null - description = "The scope of the integration (e.g (00000000-0000-0000-0000-000000000000)" -} - -variable "location" { - type = string - default = "East US" - description = "The location to deploy the container to" -} diff --git a/deployment/terraform/azure/container_app/main.tf b/deployment/terraform/azure/container_app/main.tf deleted file mode 100644 index 90e56aedd5..0000000000 --- a/deployment/terraform/azure/container_app/main.tf +++ /dev/null @@ -1,36 +0,0 @@ -locals { - prefix = "port-ocean" -} - -resource "azurerm_resource_group" "ocean-rg" { - count = var.resource_group_name != null ? 0 : 1 - name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-rg" - location = var.location -} - -module "port_ocean_authorization" { - source = "../modules/authorization" - location = var.location - resource_group_name = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name - integration = var.integration - permissions = var.permissions - subscription_id = var.subscription_id -} - -module "port_ocean_container_app" { - source= "./modules/container_app" - integration = var.integration - port = var.port - initialize_port_resources = var.initialize_port_resources - location = var.location - resource_group_name = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name - container_app_environment_id = var.container_app_environment_id - log_analytics_workspace_id = var.log_analytics_workspace_id - image = var.image - min_replicas = var.min_replicas - max_replicas = var.max_replicas - user_assigned_identity_ids = [module.port_ocean_authorization.user_assigned_identity_id] - user_assigned_client_id = module.port_ocean_authorization.user_assigned_identity_client_id - additional_secrets = var.additional_secrets - additional_environment_variables = var.additional_environment_variables -} diff --git a/deployment/terraform/azure/container_app/modules/container_app/main.tf b/deployment/terraform/azure/container_app/modules/container_app/main.tf deleted file mode 100644 index ad20443a71..0000000000 --- a/deployment/terraform/azure/container_app/modules/container_app/main.tf +++ /dev/null @@ -1,110 +0,0 @@ -locals { - prefix = "port-ocean" - env = [ - { - name = upper("OCEAN__INITIALIZE_PORT_RESOURCES"), - value = var.initialize_port_resources ? "true" : "false" - }, - { - name = upper("OCEAN__EVENT_LISTENER") - value = jsonencode({ - for key, value in var.event_listener : key => value if value != null - }) - }, - { - name = upper("OCEAN__INTEGRATION") - value = jsonencode(var.integration) - } - ] - port_credentials_secret_name = "ocean-port-credentials" -} - -resource "azurerm_log_analytics_workspace" "ocean-log-analytics" { - count = var.log_analytics_workspace_id != null ? 0 : 1 - name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-law" - location = var.location - resource_group_name = var.resource_group_name - sku = "PerGB2018" - retention_in_days = 30 -} - -resource "azurerm_container_app_environment" "ocean-container-app-env" { - count = var.container_app_environment_id != null ? 0 : 1 - name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-env" - location = var.location - resource_group_name = var.resource_group_name - log_analytics_workspace_id = var.log_analytics_workspace_id != null ? var.log_analytics_workspace_id : azurerm_log_analytics_workspace.ocean-log-analytics[0].id -} - - -resource "azurerm_container_app" "ocean-container-app" { - name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}" - container_app_environment_id = var.container_app_environment_id != null ? var.container_app_environment_id : azurerm_container_app_environment.ocean-container-app-env[0].id - resource_group_name = var.resource_group_name - revision_mode = "Single" - identity { - type = "UserAssigned" - identity_ids = var.user_assigned_identity_ids - } - ingress { - external_enabled = var.assign_public_ip - target_port = var.container_port - traffic_weight { - percentage = 100 - latest_revision = true - } - } - template { - min_replicas = 1 - max_replicas = 1 - container { - name = "${local.prefix}-${var.integration.type}" - cpu = var.cpu - memory = var.memory - image = var.image != null ? var.image : "${var.image_registry}/port-ocean-${var.integration.type}:${var.integration_version}" - dynamic "env" { - for_each = local.env - content { - name = env.value.name - value = env.value.value - } - } - dynamic "env" { - for_each = var.additional_environment_variables - content { - name = env.key - value = env.value - } - } - env { - name = "AZURE_CLIENT_ID" - value = var.user_assigned_client_id - } - env { - name = "OCEAN__PORT" - secret_name = local.port_credentials_secret_name - } - dynamic "env" { - for_each = var.additional_secrets - content { - name = env.key - secret_name = replace("ocean-${lower(env.key)}", "_", "-") - } - } - } - } - secret { - name = local.port_credentials_secret_name - value = jsonencode({ - for key, value in var.port : key => value if value != null - }) - } - dynamic "secret" { - for_each = var.additional_secrets - content { - name = replace("ocean-${lower(secret.key)}", "_", "-") - value = secret.value - } - } -} - diff --git a/deployment/terraform/azure/container_app/modules/container_app/outputs.tf b/deployment/terraform/azure/container_app/modules/container_app/outputs.tf deleted file mode 100644 index ebab923973..0000000000 --- a/deployment/terraform/azure/container_app/modules/container_app/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "container_app_latest_fqdn" { - value = azurerm_container_app.ocean-container-app.latest_revision_fqdn -} - -output "container_app_outbound_ip_addresses" { - value = azurerm_container_app.ocean-container-app.outbound_ip_addresses -} - -output "container_latest_revision_name" { - value = azurerm_container_app.ocean-container-app.latest_revision_name -} diff --git a/deployment/terraform/azure/container_app/modules/container_app/variables.tf b/deployment/terraform/azure/container_app/modules/container_app/variables.tf deleted file mode 100644 index 4812a87553..0000000000 --- a/deployment/terraform/azure/container_app/modules/container_app/variables.tf +++ /dev/null @@ -1,150 +0,0 @@ -variable "event_listener" { - type = object({ - type = string - - # POLLING - resync_on_start = optional(bool) - interval = optional(number) - - # WEBHOOK - app_host = optional(string) - - - # KAFKA - brokers = optional(list(string)) - security_protocol = optional(list(string)) - authentication_mechanism = optional(list(string)) - kafka_security_enabled = optional(list(bool)) - consumer_poll_timeout = optional(list(number)) - }) - - default = { - type = "POLLING" - } - description = "The event listener configuration" -} - -variable "additional_secrets" { - type = map(string) - default = {} - description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: \"my-secret\"}" -} - -variable "additional_environment_variables" { - type = map(string) - default = {} - description = "Additional environment variables to be injected into the container" -} - -variable "port" { - type = object({ - client_id = string - client_secret = string - base_url = optional(string) - }) - description = "The port configuration, this is used to authenticate with the port API" -} - -variable "initialize_port_resources" { - type = bool - default = false - description = "If true, the module will create the port resources required for the integration" -} - -variable "integration_version" { - type = string - default = "latest" - description = "The version of the integration to deploy" -} - -variable "integration" { - type = object({ - identifier = optional(string) - type = string - config = map(any) - }) - description = "The integration configuration, this is used to configure the integration and register it with port" -} - -variable "assign_public_ip" { - type = bool - default = true - description = "If true, the container will be assigned a public IP" -} - -variable "container_port" { - default = 8000 - description = "The port the container will listen on" -} - -variable "image_registry" { - type = string - default = "ghcr.io/port-labs" - description = "The image registry to pull the image from" -} - -variable "location" { - type = string - default = "West US 2" - description = "The location to deploy the container to" -} - -variable "resource_group_name" { - type = string - default = null - description = "The name of the resource group to deploy the container to" -} - -variable "container_app_environment_id" { - type = string - default = null - description = "The ID of the container app environment to deploy the container to, if not provided, a new container app environment will be created" -} - -variable "log_analytics_workspace_id" { - type = string - default = null - description = "The ID of the log analytics workspace to send logs to, if not provided, log analytics will be created" -} - -variable "cpu" { - type = string - default = "1.0" - description = "The CPU to allocate to the container" -} - -variable "memory" { - type = string - default = "2Gi" - description = "The memory to allocate to the container" -} - -variable "image" { - type = string - default = null - description = "The image to deploy, if not provided, the latest image of the integration will be deployed" -} - -variable "min_replicas" { - type = number - default = 1 - description = "The minimum number of replicas to deploy" -} - -variable "max_replicas" { - type = number - default = 1 - description = "The maximum number of replicas to deploy" -} - -variable "user_assigned_identity_ids" { - type = list(string) - default = [] - description = "The IDs of the user assigned identities to assign to the container" -} - -variable "user_assigned_client_id" { - type = string - default = null - description = "The client ID of the user assigned identity to assign to the container" -} diff --git a/deployment/terraform/azure/container_app/outputs.tf b/deployment/terraform/azure/container_app/outputs.tf deleted file mode 100644 index 31402d1325..0000000000 --- a/deployment/terraform/azure/container_app/outputs.tf +++ /dev/null @@ -1,27 +0,0 @@ -output "container_app_latest_fqdn" { - value = module.port_ocean_container_app.container_app_latest_fqdn -} - -output "container_app_outbound_ip_addresses" { - value = module.port_ocean_container_app.container_app_outbound_ip_addresses -} - -output "container_latest_revision_name" { - value = module.port_ocean_container_app.container_latest_revision_name -} - -output "resource_group_name" { - value = var.resource_group_name != null ? var.resource_group_name : azurerm_resource_group.ocean-rg[0].name -} - -output "location" { - value = var.location -} - -output "subscription_id" { - value = module.port_ocean_authorization.subscription_id -} - -output "integration" { - value = var.integration -} diff --git a/deployment/terraform/azure/container_app/providers.tf b/deployment/terraform/azure/container_app/providers.tf deleted file mode 100644 index 239b407c35..0000000000 --- a/deployment/terraform/azure/container_app/providers.tf +++ /dev/null @@ -1,27 +0,0 @@ -terraform { - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "3.68.0" - } - env = { - source = "tchupp/env" - version = "0.0.2" - } - jsonschema = { - source = "bpedman/jsonschema" - version = "0.2.1" - } - } -} -provider "azurerm" { - # The AzureRM Provider supports authenticating using via the Azure CLI, a Managed Identity - # and a Service Principal. More information on the authentication methods supported by - # the AzureRM Provider can be found here: - # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure - - # The features block allows changing the behaviour of the Azure Provider, more - # information can be found here: - # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/features-block - features {} -} diff --git a/deployment/terraform/azure/container_app/variables.tf b/deployment/terraform/azure/container_app/variables.tf deleted file mode 100644 index 5fa52f8067..0000000000 --- a/deployment/terraform/azure/container_app/variables.tf +++ /dev/null @@ -1,160 +0,0 @@ -variable "event_listener" { - type = object({ - type = string - - # POLLING - resync_on_start = optional(bool) - interval = optional(number) - - # WEBHOOK - app_host = optional(string) - - - # KAFKA - brokers = optional(list(string)) - security_protocol = optional(list(string)) - authentication_mechanism = optional(list(string)) - kafka_security_enabled = optional(list(bool)) - consumer_poll_timeout = optional(list(number)) - }) - - default = { - type = "POLLING" - } - description = "The event listener configuration" -} - -variable "additional_secrets" { - type = map(string) - default = {} - description = "Additional secrets to be injected into the container, the key of the map is the name of the secret and will be the environment variable name, e.g. {MY_SECRET: \"my-secret\"}" -} - -variable "additional_environment_variables" { - type = map(string) - default = {} - description = "Additional environment variables to be injected into the container" -} - -variable "port" { - type = object({ - client_id = string - client_secret = string - base_url = optional(string) - }) - description = "The port configuration, this is used to authenticate with the port API" -} - -variable "initialize_port_resources" { - type = bool - default = false - description = "If true, the module will create the port resources required for the integration" -} - -variable "integration_version" { - type = string - default = "latest" - description = "The version of the integration to deploy" -} - -variable "integration" { - type = object({ - identifier = optional(string) - type = string - config = map(any) - }) - description = "The integration configuration, this is used to configure the integration and register it with port" -} - -variable "assign_public_ip" { - type = bool - default = true - description = "If true, the container will be assigned a public IP" -} - -variable "container_port" { - default = 8000 - description = "The port the container will listen on" -} - -variable "image_registry" { - type = string - default = "ghcr.io/port-labs" - description = "The image registry to pull the image from" -} - -variable "location" { - type = string - default = "East US 2" - description = "The location to deploy the container to" -} - -variable "subscription_id" { - type = string - default = null - description = "The scope of the user assigned identity and the scope of the role definition (e.g /subscriptions/00000000-0000-0000-0000-000000000000)" -} - -variable "resource_group_name" { - type = string - default = null - description = "The resource group to deploy the container to and where the role definition will be created" -} - -variable "container_app_environment_id" { - type = string - default = null - description = "The ID of the container app environment to deploy the container to, if not provided, a new container app environment will be created" -} - -variable "log_analytics_workspace_id" { - type = string - default = null - description = "The ID of the log analytics workspace to send logs to, if not provided, log analytics will be created" -} - -variable "cpu" { - type = string - default = "1.0" - description = "The CPU to allocate to the container" -} - -variable "memory" { - type = string - default = "2Gi" - description = "The memory to allocate to the container" -} - -variable "image" { - type = string - default = null - description = "The image to deploy, if not provided, the latest image of the integration will be deployed" -} - -variable "min_replicas" { - type = number - default = 1 - description = "The minimum number of replicas to deploy" -} - -variable "max_replicas" { - type = number - default = 1 - description = "The maximum number of replicas to deploy" -} - -variable "permissions" { - type = object({ - actions = optional(list(string)) - data_actions = optional(list(string)) - not_actions = optional(list(string)) - not_data_actions = optional(list(string)) - }) - description = "The permissions to grant to the user assigned identity" -} - -variable "permissions_scope" { - type = list(string) - default = null - description = "The scope of the permissions" -} \ No newline at end of file diff --git a/deployment/terraform/azure/modules/authorization/main.tf b/deployment/terraform/azure/modules/authorization/main.tf deleted file mode 100644 index 7b9c65a867..0000000000 --- a/deployment/terraform/azure/modules/authorization/main.tf +++ /dev/null @@ -1,31 +0,0 @@ -data "azurerm_subscription" "current_subscription" {} - -locals { - prefix = "port-ocean" - subscription_id = var.subscription_id != null ? var.subscription_id : data.azurerm_subscription.current_subscription.id -} - -resource "azurerm_role_definition" "ocean-azure-role-definition" { - name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-rd" - scope = local.subscription_id - description = "This role is used by ${var.integration.type}-${var.integration.identifier} Port Ocean integration" - permissions { - actions = var.permissions.actions - not_actions = var.permissions.not_actions - data_actions = var.permissions.data_actions - not_data_actions = var.permissions.not_data_actions - } - assignable_scopes = var.permissions_scope != null ? var.permissions_scope : [local.subscription_id] -} - -resource "azurerm_user_assigned_identity" "ocean-azure-assigned-identity" { - name = "${local.prefix}-${var.integration.type}-${var.integration.identifier}-identity" - location = var.location - resource_group_name = var.resource_group_name -} - -resource "azurerm_role_assignment" "ocean-azure-role-assignment" { - scope = data.azurerm_subscription.current_subscription.id - role_definition_id = azurerm_role_definition.ocean-azure-role-definition.role_definition_resource_id - principal_id = azurerm_user_assigned_identity.ocean-azure-assigned-identity.principal_id -} diff --git a/deployment/terraform/azure/modules/authorization/outputs.tf b/deployment/terraform/azure/modules/authorization/outputs.tf deleted file mode 100644 index 72cb0cf946..0000000000 --- a/deployment/terraform/azure/modules/authorization/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "user_assigned_identity_id" { - value = azurerm_user_assigned_identity.ocean-azure-assigned-identity.id -} - -output "user_assigned_identity_client_id" { - value = azurerm_user_assigned_identity.ocean-azure-assigned-identity.client_id -} - -output "subscription_id" { - value = local.subscription_id -} diff --git a/deployment/terraform/azure/modules/authorization/variables.tf b/deployment/terraform/azure/modules/authorization/variables.tf deleted file mode 100644 index 0e182ad235..0000000000 --- a/deployment/terraform/azure/modules/authorization/variables.tf +++ /dev/null @@ -1,48 +0,0 @@ -variable "integration_version" { - type = string - default = "latest" - description = "The version of the integration to use" -} - -variable "integration" { - type = object({ - identifier = optional(string) - type = string - config = map(any) - }) - description = "The integration to use" -} - -variable "location" { - type = string - default = "West US 2" - description = "The location to create the user assigned identity in" -} - -variable "subscription_id" { - type = string - default = null - description = "The scope of the user assigned identity and the scope of the role definition" -} - -variable "resource_group_name" { - type = string - default = null - description = "The resource group name to associate the user assigned identity with" -} - -variable "permissions" { - type = object({ - actions = optional(list(string)) - data_actions = optional(list(string)) - not_actions = optional(list(string)) - not_data_actions = optional(list(string)) - }) - description = "The permissions to grant to the user assigned identity" -} - -variable "permissions_scope" { - type = list(string) - default = null - description = "The scope of the permissions" -} diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 9be3e1d277..73917ef0c1 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc18 +version: v0.1.0 type: azure description: azure integration for Port Ocean icon: Azure @@ -18,4 +18,7 @@ configurations: type: string sensitive: true deploymentMethod: - deployments: \ No newline at end of file + - type: terraform/azure/container-app + example: azure-integration + title: Azure Container App + extraVars: [] \ No newline at end of file diff --git a/integrations/azure/README.md b/integrations/azure/README.md index 00a036941b..f5150b90f3 100644 --- a/integrations/azure/README.md +++ b/integrations/azure/README.md @@ -22,10 +22,6 @@ Before using this module, make sure you have completed the following prerequisit az login ``` - Export your [Port Credentials](https://docs.getport.io/build-your-software-catalog/sync-data-to-catalog/api/#find-your-port-credentials). - ```bash - export TF_VAR_port_client_id= - export TF_VAR_port_client_secret= - ``` ### Azure @@ -41,13 +37,13 @@ The integration will need to have permissions to read the resources you want to #### Azure Infrastructure - **Azure Subscription**: You'll need an Azure subscription to deploy the integration. -- **Azure Resource Group**: The integration need to be deployed to an Azure Resource Group, you can pass an existing one, or the integration will create a new one. (To override add `-var='resource_group_id='`) +- **Azure Resource Group**: The integration need to be deployed to an Azure Resource Group, you can pass an existing one, or the integration will create a new one. (To override add `resource_group_id=`) - **Azure Container App**: The integration will be deployed using Azure Container App. Which requires some extra infrastructure to be deployed, by default if not specified otherwise we will deploy the required infrastructure. - - **Azure Log Analytics Workspace**: The integration will create a new Log Analytics Workspace to store the logs of the integration. (To override add `-var='log_analytics_workspace_id='` to the terraform apply command) - - **Azure Container App Environment**: The integration will create a new Container App Environment to deploy the integration to. (To override add `-var='container_app_environment_id='` to the terraform apply command) + - **Azure Log Analytics Workspace**: The integration will create a new Log Analytics Workspace to store the logs of the integration. (To override add `log_analytics_workspace_id=` to the terraform apply command) + - **Azure Container App Environment**: The integration will create a new Container App Environment to deploy the integration to. (To override add `container_app_environment_id=` to the terraform apply command) #### Azure Event Grid -- **Azure Event Grid System Topic**: To allow the integration to receive events from Azure, an Event Grid System Topic of type `Microsoft.Resources.Subscriptions` will be needed. The integration will create a new System Topic and will subscribe to it. (Due to a limitation in Azure only one Event Grid System Topic of type `Microsoft.Resources.Subscriptions` can be created per subscription, so if you already have one you'll need to pass it to the integration using `-var='event_grid_system_topic_name='`) Further example on how to create the System Topic will be provided later on in the documentation. +- **Azure Event Grid System Topic**: To allow the integration to receive events from Azure, an Event Grid System Topic of type `Microsoft.Resources.Subscriptions` will be needed. The integration will create a new System Topic and will subscribe to it. (Due to a limitation in Azure only one Event Grid System Topic of type `Microsoft.Resources.Subscriptions` can be created per subscription, so if you already have one you'll need to pass it to the integration using `event_grid_system_topic_name=`) Further example on how to create the System Topic will be provided later on in the documentation. - **Azure Event Grid Subscription**: To Pass the events from the System Topic to the integration, an Event Grid Subscription will be needed. The integration will create a new Subscription and will pass the events to the integration. @@ -59,19 +55,26 @@ The integration can be triggered in two ways: - On events sent from the Azure Event Grid. - When a change in the integration configuration is detected. -To deploy the integration, run the following commands: +To deploy the integration, do the following: -```sh -export TF_VAR_port_client_id= -export TF_VAR_port_client_secret= - -git clone git@github.com:port-labs/Port-Ocean.git +Save the following code in a file named `main.tf`: -cd deployment/azure/container_app/examples +```hcl +module "ocean-containerapp_example_azure-integration" { + source = "port-labs/ocean-containerapp/azure//examples/azure-integration" + version = "0.1.0" + + port_client_id = "" + port_client_secret = "" + subscription_id = "" +} +``` + +Then run the following commands: +```sh terraform init - -terraform apply -var='subscription_id=' +terraform apply ``` ## Supported Kinds @@ -889,21 +892,45 @@ When adding new Azure resource we need to make sure our exporter will be able to To do so we will need to add a new event filter to the integration event grid subscription. For more information on [action permissions](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-definitions#actions) ### Example -Here is an example on how to apply it, as in the base example we will head to the `deployment/azure/container_app/examples` directory +Here is an example on how to apply it, for more information on the different inputs please refer to our terraform module [documentation](https://registry.terraform.io/modules/port-labs/ocean-containerapp/azure/latest/examples/azure-integration). and apply the terraform changes. `Microsoft.Network/virtualNetworks` is the resource we want to add. -```sh -export TF_VAR_port_client_id= -export TF_VAR_port_client_secret= - -git clone git@github.com:port-labs/Port-Ocean.git -cd deployment/azure/container_app/examples +Edit the `main.tf` file and add the following: + +```hcl +module "ocean-containerapp_example_azure-integration" { + source = "port-labs/ocean-containerapp/azure//examples/azure-integration" + version = "0.1.0" + + port_client_id = "" + port_client_secret = "" + subscription_id = "" + event_grid_event_filter_list = [ + "Microsoft.App/containerApp", + "Microsoft.Storage/storageAccounts", + "Microsoft.Compute/virtualMachines", + "Microsoft.Network/loadBalancers", + "Microsoft.Resources/subscriptions/resourceGroups", + "Microsoft.Network/virtualNetworks" + ] + action_permissions_list = [ + "Microsoft.app/containerapps/read", + "Microsoft.Storage/storageAccounts/*/read", + "Microsoft.ContainerService/managedClusters/read", + "Microsoft.Network/loadBalancers/read", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Resources/subscriptions/resources/read", + "Microsoft.Network/virtualNetworks/read" + ] +} +``` +```sh +az login terraform init - -terraform apply -var='action_permissions_list=["Microsoft.Network/virtualNetworks/read", ]' -var='event_grid_event_filter_list=["Microsoft.Network/virtualNetworks",]' -var='subscription_id=' +terraform apply ``` Let's go over the changes we made: From 75346cf20e6dccabbc28aa2bb11280769b08db60 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 17 Aug 2023 15:30:41 +0300 Subject: [PATCH 93/95] update docs --- integrations/azure/.port/spec.yaml | 2 +- integrations/azure/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index 73917ef0c1..b92556519e 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0 +version: v0.1.0rc19 type: azure description: azure integration for Port Ocean icon: Azure diff --git a/integrations/azure/README.md b/integrations/azure/README.md index f5150b90f3..adb0da1e99 100644 --- a/integrations/azure/README.md +++ b/integrations/azure/README.md @@ -62,7 +62,7 @@ Save the following code in a file named `main.tf`: ```hcl module "ocean-containerapp_example_azure-integration" { source = "port-labs/ocean-containerapp/azure//examples/azure-integration" - version = "0.1.0" + version = "~>0.0.2" port_client_id = "" port_client_secret = "" @@ -902,7 +902,7 @@ Edit the `main.tf` file and add the following: ```hcl module "ocean-containerapp_example_azure-integration" { source = "port-labs/ocean-containerapp/azure//examples/azure-integration" - version = "0.1.0" + version = "~>0.0.2" port_client_id = "" port_client_secret = "" From 4624832a6908eee394a3e50caec22fb3ad471515 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Thu, 17 Aug 2023 20:20:38 +0300 Subject: [PATCH 94/95] bump to 0.1.0 --- .../container_app/examples/azure-integration/main.tf | 8 ++++++++ integrations/azure/.port/spec.yaml | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 deployment/terraform/azure/container_app/examples/azure-integration/main.tf diff --git a/deployment/terraform/azure/container_app/examples/azure-integration/main.tf b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf new file mode 100644 index 0000000000..b116776c27 --- /dev/null +++ b/deployment/terraform/azure/container_app/examples/azure-integration/main.tf @@ -0,0 +1,8 @@ +module "ocean-containerapp_example_azure-integration" { + source = "port-labs/ocean-containerapp/azure//examples/azure-integration" + version = ">=0.0.4" + + port_client_id = "" + port_client_secret = "" + subscription_id = "xsa23scckw-1c79-26fa-a3f1-dnsadbjasd1" +} \ No newline at end of file diff --git a/integrations/azure/.port/spec.yaml b/integrations/azure/.port/spec.yaml index b92556519e..5275ab170d 100644 --- a/integrations/azure/.port/spec.yaml +++ b/integrations/azure/.port/spec.yaml @@ -1,4 +1,4 @@ -version: v0.1.0rc19 +version: v0.1.0 type: azure description: azure integration for Port Ocean icon: Azure @@ -18,7 +18,9 @@ configurations: type: string sensitive: true deploymentMethod: - - type: terraform/azure/container-app + - type: terraform + module: ocean-containerapp example: azure-integration title: Azure Container App + version: ">=0.0.4" extraVars: [] \ No newline at end of file From ecc71c336c1c2a1081fb8ac19ca46c070ca5fad5 Mon Sep 17 00:00:00 2001 From: tankilevitch Date: Fri, 18 Aug 2023 18:52:35 +0300 Subject: [PATCH 95/95] CR fixes --- .../azure/azure_integration/exceptions.py | 9 -- integrations/azure/azure_integration/ocean.py | 83 ++++++++++++------- integrations/azure/integration.py | 27 ------ integrations/azure/main.py | 8 -- integrations/azure/poetry.lock | 2 +- integrations/azure/pyproject.toml | 1 - 6 files changed, 53 insertions(+), 77 deletions(-) delete mode 100644 integrations/azure/azure_integration/exceptions.py diff --git a/integrations/azure/azure_integration/exceptions.py b/integrations/azure/azure_integration/exceptions.py deleted file mode 100644 index 5e2586e173..0000000000 --- a/integrations/azure/azure_integration/exceptions.py +++ /dev/null @@ -1,9 +0,0 @@ -from port_ocean.exceptions.base import BaseOceanException - - -class AzureIntegrationException(BaseOceanException): - pass - - -class AzureIntegrationNotFoundKindInPortAppConfig(AzureIntegrationException): - pass diff --git a/integrations/azure/azure_integration/ocean.py b/integrations/azure/azure_integration/ocean.py index b6ff334067..61f8079746 100644 --- a/integrations/azure/azure_integration/ocean.py +++ b/integrations/azure/azure_integration/ocean.py @@ -4,7 +4,7 @@ from cloudevents.pydantic import CloudEvent import fastapi from loguru import logger - +from starlette import responses from port_ocean.context.ocean import ocean from port_ocean.core.models import Entity from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE @@ -29,19 +29,15 @@ @ocean.on_resync() async def resync_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - with logger.contextualize(resource_kind=kind): - logger.info("Entered resync of base resources", kind=kind) - if kind in iter(ResourceKindsWithSpecialHandling) or is_sub_resource(kind): - logger.info("Kind is not a base resource, skipping resync", kind=kind) - return - async with resource_client_context() as client: - async for resources_batch in batch_resources_iterator( - list_resources, - resources_client=client, - resource_type=kind, - api_version=get_current_resource_config().selector.api_version, - ): - yield resources_batch + if kind in iter(ResourceKindsWithSpecialHandling): + logger.info("Kind already has a specific handling, skipping", kind=kind) + return + if is_sub_resource(kind): + iterator_resync_method = resync_extension_resources + else: + iterator_resync_method = resync_base_resources + async for resources_batch in iterator_resync_method(kind): + yield resources_batch @ocean.on_resync(kind=ResourceKindsWithSpecialHandling.RESOURCE_GROUPS) @@ -49,20 +45,28 @@ async def resync_resource_groups(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: """ Re-syncs resource groups, this is done separately because the resource groups api is different from the other apis """ - with logger.contextualize(resource_kind=kind): - logger.info("Entered resync of resource groups", kind=kind) - async with DefaultAzureCredential() as credential: - async with ResourceManagementClient( - credential=credential, subscription_id=get_integration_subscription_id() - ) as client: - async for resource_groups_batch in batch_resources_iterator( - client.resource_groups.list, - api_version=get_current_resource_config().selector.api_version, - ): - yield resource_groups_batch + async with DefaultAzureCredential() as credential: + async with ResourceManagementClient( + credential=credential, subscription_id=get_integration_subscription_id() + ) as client: + async for resource_groups_batch in batch_resources_iterator( + client.resource_groups.list, + api_version=get_current_resource_config().selector.api_version, + ): + yield resource_groups_batch + + +async def resync_base_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: + async with resource_client_context() as client: + async for resources_batch in batch_resources_iterator( + list_resources, + resources_client=client, + resource_type=kind, + api_version=get_current_resource_config().selector.api_version, + ): + yield resources_batch -@ocean.on_resync() async def resync_extension_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: """ Re-syncs sub resources @@ -91,11 +95,6 @@ async def resync_extension_resources(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: :return: Async generator of extension resources """ with logger.contextualize(resource_kind=kind): - logger.info("Entered resync of extension resources", kind=kind) - if not is_sub_resource(kind) or kind in iter(ResourceKindsWithSpecialHandling): - logger.info("Kind is not an extension resource, skipping resync", kind=kind) - return - async with resource_client_context() as client: base_resource_kind, _ = get_resource_kind_by_level(kind, 0) api_version = get_current_resource_config().selector.api_version @@ -238,3 +237,25 @@ async def handle_events(cloud_event: CloudEvent) -> fastapi.Response: return fastapi.Response(status_code=http.HTTPStatus.OK) await ocean.register_raw(resource_type, [resource.as_dict()]) # type: ignore return fastapi.Response(status_code=http.HTTPStatus.OK) + + +@ocean.app.fast_api_app.middleware("azure_cloud_event") +async def cloud_event_validation_middleware_handler( + request: fastapi.Request, + call_next: typing.Callable[[fastapi.Request], typing.Awaitable[responses.Response]], +) -> responses.Response: + """ + Middleware used to handle cloud event validation requests + Azure topic subscription expects a 200 response with specific headers + https://github.com/cloudevents/spec/blob/v1.0/http-webhook.md#42-validation-response + """ + if request.method == "OPTIONS" and request.url.path.startswith("/integration"): + logger.info("Detected cloud event validation request") + headers = { + "WebHook-Allowed-Rate": "100", + "WebHook-Allowed-Origin": "*", + } + response = fastapi.Response(status_code=200, headers=headers) + return response + + return await call_next(request) diff --git a/integrations/azure/integration.py b/integrations/azure/integration.py index fcc941fa05..1e42015f12 100644 --- a/integrations/azure/integration.py +++ b/integrations/azure/integration.py @@ -1,9 +1,3 @@ -from requests import Request # type: ignore -from starlette import responses -from typing import Awaitable, Callable - -import fastapi -from loguru import logger from port_ocean.core.handlers.port_app_config.api import APIPortAppConfig from port_ocean.core.integrations.base import BaseIntegration @@ -13,24 +7,3 @@ class AzureIntegration(BaseIntegration): class AppConfigHandlerClass(APIPortAppConfig): CONFIG_CLASS = AzurePortAppConfig - - -async def cloud_event_validation_middleware_handler( - request: Request, - call_next: Callable[[Request], Awaitable[responses.Response]], -) -> responses.Response: - """ - Middleware used to handle cloud event validation requests - Azure topic subscription expects a 200 response with specific headers - https://github.com/cloudevents/spec/blob/v1.0/http-webhook.md#42-validation-response - """ - if request.method == "OPTIONS" and request.url.path.startswith("/integration"): - logger.info("Detected cloud event validation request") - headers = { - "WebHook-Allowed-Rate": "100", - "WebHook-Allowed-Origin": "*", - } - response = fastapi.Response(status_code=200, headers=headers) - return response - - return await call_next(request) diff --git a/integrations/azure/main.py b/integrations/azure/main.py index f1db166cae..25a74944b7 100644 --- a/integrations/azure/main.py +++ b/integrations/azure/main.py @@ -1,11 +1,3 @@ # noinspection PyUnresolvedReferences # ruff: noqa: F401 from azure_integration import ocean -from integration import cloud_event_validation_middleware_handler - -from port_ocean.context.ocean import ocean as port_ocean - - -port_ocean.app.fast_api_app.middleware("azure_cloud_event")( - cloud_event_validation_middleware_handler -) diff --git a/integrations/azure/poetry.lock b/integrations/azure/poetry.lock index f8c1c68aa5..dd042c4e59 100644 --- a/integrations/azure/poetry.lock +++ b/integrations/azure/poetry.lock @@ -2070,4 +2070,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "0ca3f89fb5a6af1e74a64311749b4b501021b9cca3b8c91344055d9bd8f301dd" +content-hash = "81cdf1cede7911a06972139348c0c954a327fd1f463a69e548f68dce660e7309" diff --git a/integrations/azure/pyproject.toml b/integrations/azure/pyproject.toml index ccd30616f0..48d19eadfe 100644 --- a/integrations/azure/pyproject.toml +++ b/integrations/azure/pyproject.toml @@ -12,7 +12,6 @@ azure-mgmt-resource = "23.0.1" azure-identity = "^1.13.0" aiohttp = "^3.7.4" cloudevents = "^1.9.0" -requests = "^2.31.0" [tool.poetry.group.dev.dependencies] pytest = "^7.2"