From 38ea9e3d590983e25efae062a7a3e8071feb5236 Mon Sep 17 00:00:00 2001 From: Hanxiang Gao Date: Tue, 1 Jun 2021 23:38:51 +0800 Subject: [PATCH] feat: Add initial code of skyline-apiserver Add initial code of skyline-apiserver Change-Id: Ib425960b707237193fd8531fb3989f29282f5b58 --- .dockerignore | 83 + .flake8 | 5 + .gitignore | 77 + LICENSE | 177 + Makefile | 136 + README-zh_CN.md | 36 + README.md | 36 + alembic.ini | 37 + docs/api/swagger.json | 2979 ++++++++++++++++ etc/.gitignore | 2 + etc/gunicorn.py | 8 + libs/skyline-log/.flake8 | 5 + libs/skyline-log/LICENSE | 177 + libs/skyline-log/Makefile | 35 + libs/skyline-log/mypy.ini | 3 + libs/skyline-log/poetry.lock | 502 +++ libs/skyline-log/poetry.toml | 2 + libs/skyline-log/pyproject.toml | 54 + libs/skyline-log/src/skyline_log/__init__.py | 19 + libs/skyline-log/src/skyline_log/log.py | 82 + libs/skyline-log/tests/__init__.py | 0 libs/skyline-log/tests/test_log.py | 0 libs/skyline-log/tests/test_skyline_log.py | 19 + libs/skyline-policy-manager/.flake8 | 5 + libs/skyline-policy-manager/LICENSE | 177 + libs/skyline-policy-manager/Makefile | 37 + libs/skyline-policy-manager/mypy.ini | 4 + libs/skyline-policy-manager/poetry.lock | 2269 ++++++++++++ libs/skyline-policy-manager/poetry.toml | 2 + libs/skyline-policy-manager/pyproject.toml | 64 + .../src/skyline_policy_manager/__init__.py | 15 + .../src/skyline_policy_manager/__main__.py | 44 + .../skyline_policy_manager/cmd/__init__.py | 0 .../src/skyline_policy_manager/cmd/manage.py | 284 ++ .../policies/__init__.py | 38 + .../skyline_policy_manager/policies/base.py | 121 + .../skyline_policy_manager/policies/cinder.py | 1245 +++++++ .../skyline_policy_manager/policies/glance.py | 381 ++ .../skyline_policy_manager/policies/heat.py | 911 +++++ .../skyline_policy_manager/policies/ironic.py | 1096 ++++++ .../policies/keystone.py | 2193 ++++++++++++ .../policies/neutron.py | 2228 ++++++++++++ .../skyline_policy_manager/policies/nova.py | 1647 +++++++++ .../policies/octavia.py | 700 ++++ .../skyline_policy_manager/policies/panko.py | 35 + .../policies/placement.py | 261 ++ .../src/skyline_policy_manager/schema.py | 56 + libs/skyline-policy-manager/tests/__init__.py | 0 .../tests/test_skyline_policy_manager.py | 19 + .../tools/post_install.sh | 30 + mypy.ini | 37 + poetry.lock | 3087 +++++++++++++++++ poetry.toml | 2 + pyproject.toml | 97 + src/skyline_apiserver/__init__.py | 15 + src/skyline_apiserver/__main__.py | 34 + src/skyline_apiserver/api/__init__.py | 0 src/skyline_apiserver/api/deps.py | 83 + src/skyline_apiserver/api/v1/__init__.py | 24 + src/skyline_apiserver/api/v1/contrib.py | 112 + src/skyline_apiserver/api/v1/extension.py | 1052 ++++++ src/skyline_apiserver/api/v1/login.py | 190 + .../api/v1/openstack/__init__.py | 0 .../api/v1/openstack/base.py | 240 ++ src/skyline_apiserver/api/v1/policy.py | 92 + src/skyline_apiserver/api/v1/setting.py | 140 + src/skyline_apiserver/api/v1/utils.py | 263 ++ src/skyline_apiserver/client/__init__.py | 0 .../client/openstack/__init__.py | 0 .../client/openstack/cinder.py | 83 + .../client/openstack/glance.py | 48 + .../client/openstack/keystone.py | 73 + .../client/openstack/neutron.py | 41 + .../client/openstack/nova.py | 86 + .../client/openstack/system.py | 92 + src/skyline_apiserver/client/utils.py | 151 + src/skyline_apiserver/cmd/__init__.py | 0 src/skyline_apiserver/cmd/generate_swagger.py | 61 + src/skyline_apiserver/config/__init__.py | 38 + src/skyline_apiserver/config/base.py | 152 + src/skyline_apiserver/config/default.py | 91 + src/skyline_apiserver/config/developer.py | 31 + src/skyline_apiserver/config/openstack.py | 218 ++ src/skyline_apiserver/config/setting.py | 113 + src/skyline_apiserver/core/__init__.py | 0 src/skyline_apiserver/core/security.py | 142 + src/skyline_apiserver/db/__init__.py | 18 + src/skyline_apiserver/db/alembic/README | 1 + src/skyline_apiserver/db/alembic/__init__.py | 0 src/skyline_apiserver/db/alembic/env.py | 81 + .../db/alembic/script.py.mako | 24 + .../db/alembic/versions/000_init.py | 55 + .../db/alembic/versions/__init__.py | 0 src/skyline_apiserver/db/api.py | 123 + src/skyline_apiserver/db/base.py | 53 + src/skyline_apiserver/db/models.py | 34 + src/skyline_apiserver/main.py | 61 + src/skyline_apiserver/network/__init__.py | 0 src/skyline_apiserver/network/neutron.py | 53 + src/skyline_apiserver/policies/__init__.py | 39 + src/skyline_apiserver/policies/base.py | 116 + src/skyline_apiserver/schemas/__init__.py | 73 + src/skyline_apiserver/schemas/common.py | 45 + src/skyline_apiserver/schemas/contrib.py | 22 + src/skyline_apiserver/schemas/extension.py | 517 +++ src/skyline_apiserver/schemas/login.py | 126 + src/skyline_apiserver/schemas/policy.py | 32 + src/skyline_apiserver/schemas/setting.py | 33 + src/skyline_apiserver/types/__init__.py | 33 + src/skyline_apiserver/types/constants.py | 41 + src/skyline_apiserver/utils/__init__.py | 0 src/skyline_apiserver/utils/coroutine.py | 29 + src/skyline_apiserver/utils/httpclient.py | 94 + src/skyline_apiserver/utils/jsonschema.py | 24 + src/skyline_apiserver/utils/roles.py | 59 + tests/api/__init__.py | 0 tests/api/v1/__init__.py | 0 tests/api/v1/test_contrib.py | 49 + tests/api/v1/test_extension.py | 278 ++ tests/api/v1/test_login.py | 135 + tests/api/v1/test_setting.py | 118 + tests/conftest.py | 42 + tests/core/__init__.py | 0 tests/core/config/__init__.py | 0 tests/core/config/backup_test_base.py | 143 + tests/core/config/backup_test_default.py | 35 + tests/core/config/backup_test_openstack.py | 44 + tests/utils/__init__.py | 0 tests/utils/utils.py | 101 + tools/git_config/commit_message.txt | 20 + 130 files changed, 27975 insertions(+) create mode 100644 .dockerignore create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README-zh_CN.md create mode 100644 README.md create mode 100644 alembic.ini create mode 100644 docs/api/swagger.json create mode 100644 etc/.gitignore create mode 100644 etc/gunicorn.py create mode 100644 libs/skyline-log/.flake8 create mode 100644 libs/skyline-log/LICENSE create mode 100644 libs/skyline-log/Makefile create mode 100644 libs/skyline-log/mypy.ini create mode 100644 libs/skyline-log/poetry.lock create mode 100644 libs/skyline-log/poetry.toml create mode 100644 libs/skyline-log/pyproject.toml create mode 100644 libs/skyline-log/src/skyline_log/__init__.py create mode 100644 libs/skyline-log/src/skyline_log/log.py create mode 100644 libs/skyline-log/tests/__init__.py create mode 100644 libs/skyline-log/tests/test_log.py create mode 100644 libs/skyline-log/tests/test_skyline_log.py create mode 100644 libs/skyline-policy-manager/.flake8 create mode 100644 libs/skyline-policy-manager/LICENSE create mode 100644 libs/skyline-policy-manager/Makefile create mode 100644 libs/skyline-policy-manager/mypy.ini create mode 100644 libs/skyline-policy-manager/poetry.lock create mode 100644 libs/skyline-policy-manager/poetry.toml create mode 100644 libs/skyline-policy-manager/pyproject.toml create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/__init__.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/__main__.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/cmd/__init__.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/cmd/manage.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/__init__.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/base.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/cinder.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/glance.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/heat.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/ironic.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/keystone.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/neutron.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/nova.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/octavia.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/panko.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/policies/placement.py create mode 100644 libs/skyline-policy-manager/src/skyline_policy_manager/schema.py create mode 100644 libs/skyline-policy-manager/tests/__init__.py create mode 100644 libs/skyline-policy-manager/tests/test_skyline_policy_manager.py create mode 100755 libs/skyline-policy-manager/tools/post_install.sh create mode 100644 mypy.ini create mode 100644 poetry.lock create mode 100644 poetry.toml create mode 100644 pyproject.toml create mode 100644 src/skyline_apiserver/__init__.py create mode 100644 src/skyline_apiserver/__main__.py create mode 100644 src/skyline_apiserver/api/__init__.py create mode 100644 src/skyline_apiserver/api/deps.py create mode 100644 src/skyline_apiserver/api/v1/__init__.py create mode 100644 src/skyline_apiserver/api/v1/contrib.py create mode 100644 src/skyline_apiserver/api/v1/extension.py create mode 100644 src/skyline_apiserver/api/v1/login.py create mode 100644 src/skyline_apiserver/api/v1/openstack/__init__.py create mode 100644 src/skyline_apiserver/api/v1/openstack/base.py create mode 100644 src/skyline_apiserver/api/v1/policy.py create mode 100644 src/skyline_apiserver/api/v1/setting.py create mode 100644 src/skyline_apiserver/api/v1/utils.py create mode 100644 src/skyline_apiserver/client/__init__.py create mode 100644 src/skyline_apiserver/client/openstack/__init__.py create mode 100644 src/skyline_apiserver/client/openstack/cinder.py create mode 100644 src/skyline_apiserver/client/openstack/glance.py create mode 100644 src/skyline_apiserver/client/openstack/keystone.py create mode 100644 src/skyline_apiserver/client/openstack/neutron.py create mode 100644 src/skyline_apiserver/client/openstack/nova.py create mode 100644 src/skyline_apiserver/client/openstack/system.py create mode 100644 src/skyline_apiserver/client/utils.py create mode 100644 src/skyline_apiserver/cmd/__init__.py create mode 100644 src/skyline_apiserver/cmd/generate_swagger.py create mode 100644 src/skyline_apiserver/config/__init__.py create mode 100644 src/skyline_apiserver/config/base.py create mode 100644 src/skyline_apiserver/config/default.py create mode 100644 src/skyline_apiserver/config/developer.py create mode 100644 src/skyline_apiserver/config/openstack.py create mode 100644 src/skyline_apiserver/config/setting.py create mode 100644 src/skyline_apiserver/core/__init__.py create mode 100644 src/skyline_apiserver/core/security.py create mode 100644 src/skyline_apiserver/db/__init__.py create mode 100644 src/skyline_apiserver/db/alembic/README create mode 100644 src/skyline_apiserver/db/alembic/__init__.py create mode 100644 src/skyline_apiserver/db/alembic/env.py create mode 100644 src/skyline_apiserver/db/alembic/script.py.mako create mode 100644 src/skyline_apiserver/db/alembic/versions/000_init.py create mode 100644 src/skyline_apiserver/db/alembic/versions/__init__.py create mode 100644 src/skyline_apiserver/db/api.py create mode 100644 src/skyline_apiserver/db/base.py create mode 100644 src/skyline_apiserver/db/models.py create mode 100644 src/skyline_apiserver/main.py create mode 100644 src/skyline_apiserver/network/__init__.py create mode 100644 src/skyline_apiserver/network/neutron.py create mode 100644 src/skyline_apiserver/policies/__init__.py create mode 100644 src/skyline_apiserver/policies/base.py create mode 100644 src/skyline_apiserver/schemas/__init__.py create mode 100644 src/skyline_apiserver/schemas/common.py create mode 100644 src/skyline_apiserver/schemas/contrib.py create mode 100644 src/skyline_apiserver/schemas/extension.py create mode 100644 src/skyline_apiserver/schemas/login.py create mode 100644 src/skyline_apiserver/schemas/policy.py create mode 100644 src/skyline_apiserver/schemas/setting.py create mode 100644 src/skyline_apiserver/types/__init__.py create mode 100644 src/skyline_apiserver/types/constants.py create mode 100644 src/skyline_apiserver/utils/__init__.py create mode 100644 src/skyline_apiserver/utils/coroutine.py create mode 100644 src/skyline_apiserver/utils/httpclient.py create mode 100644 src/skyline_apiserver/utils/jsonschema.py create mode 100644 src/skyline_apiserver/utils/roles.py create mode 100644 tests/api/__init__.py create mode 100644 tests/api/v1/__init__.py create mode 100644 tests/api/v1/test_contrib.py create mode 100644 tests/api/v1/test_extension.py create mode 100644 tests/api/v1/test_login.py create mode 100644 tests/api/v1/test_setting.py create mode 100644 tests/conftest.py create mode 100644 tests/core/__init__.py create mode 100644 tests/core/config/__init__.py create mode 100644 tests/core/config/backup_test_base.py create mode 100644 tests/core/config/backup_test_default.py create mode 100644 tests/core/config/backup_test_openstack.py create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/utils.py create mode 100644 tools/git_config/commit_message.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..705ca95 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,83 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +sql_app.db + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ +.mypy_cache/ + +# Translations +*.mo +*.pot + +# Flask stuff: +.webassets-cache + +# Sphinx documentation +docs/_build/ + +# pyenv +.python-version + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Development +.vscode/ +/log/ +tmp/ + +# MAC OS +.DS_Store +.idea + +# Remove build context +.git +.gitreview +docs +tests +requirements.txt diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e5582fa --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 99 +max-doc-length = 99 +show-source = True +extend-ignore = E203 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ad3f14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +sql_app.db + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ +.mypy_cache/ + +# Translations +*.mo +*.pot + +# Flask stuff: +.webassets-cache + +# Sphinx documentation +docs/_build/ + +# pyenv +.python-version + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Development +.vscode/ +/log/ +tmp/ + +# MAC OS +.DS_Store +.idea +requirements.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..12ec073 --- /dev/null +++ b/Makefile @@ -0,0 +1,136 @@ +PYTHON ?= python3 +SOURCES := src +LIBS := libs +TESTS := tests +TOOLS := tools +ROOT_DIR ?= $(shell git rev-parse --show-toplevel) + +# Color +no_color = \033[0m +black = \033[0;30m +red = \033[0;31m +green = \033[0;32m +yellow = \033[0;33m +blue = \033[0;34m +purple = \033[0;35m +cyan = \033[0;36m +white = \033[0;37m + +# Version +RELEASE_VERSION ?= $(shell git rev-parse --short HEAD)_$(shell date -u +%Y-%m-%dT%H:%M:%S%z) +GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) +GIT_COMMIT ?= $(shell git rev-parse --verify HEAD) + + +.PHONY: help +help: + @echo "Skyline API server development makefile" + @echo + @echo "Usage: make " + @echo + @echo "Target:" + @echo " git_config Initialize git configuration." + @echo " venv Create virtualenvs." + @echo " install Installs the project dependencies." + @echo " build Build source and wheel packages." + @echo " lint Check python code." + @echo " fmt Format python code style." + @echo " test Run unit tests." + @echo " db_revision Generate database alembic version revision with model." + @echo " db_sync Sync database from alembic version revision." + @echo " swagger Generate swagger json file." + @echo " future_check Find python files without 'type annotations'.(Alpha)" + @echo + + +.PHONY: git_config +user_name = $(shell git config --get user.name) +user_email = $(shell git config --get user.email) +commit_template = $(shell git config --get commit.template) +git_config: +ifeq ($(user_name),) + @printf "$(cyan)\n" + @read -p "Set your git user name: " user_name; \ + git config --local user.name $$user_name; \ + printf "$(green)User name was set.\n$(cyan)" +endif +ifeq ($(user_email),) + @printf "$(cyan)\n" + @read -p "Set your git email address: " user_email; \ + git config --local user.email $$user_email; \ + printf "$(green)User email address was set.\n$(no_color)" +endif +ifeq ($(commit_template),) + @git config --local commit.template $(ROOT_DIR)/tools/git_config/commit_message.txt +endif + @printf "$(green)Project git config was successfully set.\n$(no_color)" + @printf "${yellow}You may need to run 'pip install git-review' install git review tools.\n\n${no_color}" + + +.PHONY: venv +venv: git_config + poetry env use $(PYTHON) + + +.PHONY: install +install: venv + poetry run pip install -U pip + poetry run pip install -U setuptools + poetry install -vvv + $(MAKE) -C $(LIBS)/skyline-policy-manager install + $(MAKE) -C $(LIBS)/skyline-log install + + +.PHONY: build +build: + $(MAKE) -C $(LIBS)/skyline-policy-manager build + $(MAKE) -C $(LIBS)/skyline-log build + poetry build + + +.PHONY: lint +lint: + $(MAKE) -C $(LIBS)/skyline-policy-manager lint + $(MAKE) -C $(LIBS)/skyline-log lint + # poetry run mypy --no-incremental $(SOURCES) + poetry run isort --check-only --diff $(SOURCES) $(TESTS) $(TOOLS) + poetry run black --check --diff --color $(SOURCES) $(TESTS) $(TOOLS) + poetry run flake8 $(SOURCES) $(TESTS) $(TOOLS) + + +.PHONY: fmt +fmt: + $(MAKE) -C $(LIBS)/skyline-policy-manager fmt + $(MAKE) -C $(LIBS)/skyline-log fmt + poetry run isort $(SOURCES) $(TESTS) $(TOOLS) + poetry run black $(SOURCES) $(TESTS) $(TOOLS) + poetry run add-trailing-comma --py36-plus --exit-zero-even-if-changed `find $(SOURCES) $(TESTS) $(TOOLS) -name '*.py'` + + +.PHONY: test +test: + echo null + + +.PHONY: db_revision +HEAD_REV ?= $(shell poetry run alembic heads | awk '{print $$1}') +NEW_REV ?= $(shell python3 -c 'import sys; print(f"{int(sys.argv[1])+1:03}")' $(HEAD_REV)) +REV_MEG ?= +db_revision: + $(shell [ -z "$(REV_MEG)" ] && printf '$(red)Missing required message, use "make db_revision REV_MEG="$(no_color)') + poetry run alembic revision --autogenerate --rev-id $(NEW_REV) -m '$(REV_MEG)' + + +.PHONY: db_sync +db_sync: + poetry run alembic upgrade head + + +.PHONY: swagger +swagger: + poetry run swagger-generator -o $(ROOT_DIR)/docs/api/swagger.json + + +# Find python files without "type annotations" +future_check: + @find src ! -size 0 -type f -name *.py -exec grep -L 'from __future__ import annotations' {} \; diff --git a/README-zh_CN.md b/README-zh_CN.md new file mode 100644 index 0000000..f355e9f --- /dev/null +++ b/README-zh_CN.md @@ -0,0 +1,36 @@ +# Skyline API + +[English](./README.md) | 简体中文 + +## 快速开始 + +### 依赖工具 + +- make >= 3.82 +- poetry >= 1.1.0 + ([安装指南](https://python-poetry.org/docs/#installation)) + +### 开发模式 + +**仅支持 Linux (由于 uvloop 和 cython 库)** + +```bash +make install +cp etc/skyline_apiserver.yaml.sample etc/skyline_apiserver.yaml +export OS_CONFIG_DIR=$(pwd)/etc +rm -f /tmp/skyline_apiserver.db +make db_sync +``` + +```console +# $ poetry run gunicorn -c etc/gunicorn.py --reload skyline_apiserver.main:app +$ poetry run uvicorn --reload --port 28000 --log-level debug skyline_apiserver.main:app + +INFO: Uvicorn running on http://127.0.0.1:28000 (Press CTRL+C to quit) +INFO: Started reloader process [154033] using statreload +INFO: Started server process [154037] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +此时可访问在线 API 文档:`http://127.0.0.1:28000/docs` diff --git a/README.md b/README.md new file mode 100644 index 0000000..287119b --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Skyline API + +English | [简体中文](./README-zh_CN.md) + +## Quick Start + +### Dependent tools + +- make >= 3.82 +- poetry >= 1.1.0 + ([Installation Guide](https://python-poetry.org/docs/#installation)) + +### Development mode + +**Only support Linux (Because uvloop & cython)** + +```bash +make install +cp etc/skyline_apiserver.yaml.sample etc/skyline_apiserver.yaml +export OS_CONFIG_DIR=$(pwd)/etc +rm -f /tmp/skyline_apiserver.db +make db_sync +``` + +```console +# $ poetry run gunicorn -c etc/gunicorn.py --reload skyline_apiserver.main:app +$ poetry run uvicorn --reload --port 28000 --log-level debug skyline_apiserver.main:app + +INFO: Uvicorn running on http://127.0.0.1:28000 (Press CTRL+C to quit) +INFO: Started reloader process [154033] using statreload +INFO: Started server process [154037] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +You can now access the online API documentation: `http://127.0.0.1:28000/docs` diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..7355872 --- /dev/null +++ b/alembic.ini @@ -0,0 +1,37 @@ +# A generic, single database configuration. +[alembic] +# path to migration scripts +script_location = skyline_apiserver.db:alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# timezone to use when rendering the date +# within the migration file as well as the filename. +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; this defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path +# version_locations = %(here)s/bar %(here)s/bat alembic/versions + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = diff --git a/docs/api/swagger.json b/docs/api/swagger.json new file mode 100644 index 0000000..a6a5691 --- /dev/null +++ b/docs/api/swagger.json @@ -0,0 +1,2979 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Skyline API", + "version": "0.1.0" + }, + "paths": { + "/api/v1/login": { + "post": { + "tags": [ + "Login" + ], + "summary": "Login", + "description": "Login & get user profile.", + "operationId": "login_api_v1_login_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Credential" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/profile": { + "get": { + "tags": [ + "Login" + ], + "summary": "Get Profile", + "description": "Get user profile.", + "operationId": "get_profile_api_v1_profile_get", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + } + } + } + }, + "/api/v1/logout": { + "post": { + "tags": [ + "Login" + ], + "summary": "Logout", + "description": "Log out.", + "operationId": "logout_api_v1_logout_post", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OK" + } + } + } + } + } + } + }, + "/api/v1/switch_project/{project_id}": { + "post": { + "tags": [ + "Login" + ], + "summary": "Switch Project", + "description": "Switch project.", + "operationId": "switch_project_api_v1_switch_project__project_id__post", + "parameters": [ + { + "required": true, + "schema": { + "title": "Project Id", + "type": "string" + }, + "name": "project_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Profile" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/extension/servers": { + "get": { + "tags": [ + "Extension" + ], + "summary": "List Servers", + "description": "\nList Servers.\n\n*Notes*:\n- The `host` of **sort_keys** is only used for admin/system_admin role users.\n- The `name` is to support for fuzzy queries.\n", + "operationId": "list_servers_api_v1_extension_servers_get", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "exclusiveMinimum": 0.0, + "type": "integer" + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Marker", + "type": "string" + }, + "name": "marker", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtSortDir" + }, + "name": "sort_dirs", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtServerSortKey" + } + }, + "name": "sort_keys", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "All Projects", + "type": "boolean" + }, + "name": "all_projects", + "in": "query" + }, + { + "description": "Only works when the all_projects filter is also specified.", + "required": false, + "schema": { + "title": "Project Id", + "type": "string", + "description": "Only works when the all_projects filter is also specified." + }, + "name": "project_id", + "in": "query" + }, + { + "description": "Only works when the all_projects filter is also specified.", + "required": false, + "schema": { + "title": "Project Name", + "type": "string", + "description": "Only works when the all_projects filter is also specified." + }, + "name": "project_name", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Name", + "type": "string" + }, + "name": "name", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtServerStatus" + }, + "name": "status", + "in": "query" + }, + { + "description": "It will be ignored for non-admin user.", + "required": false, + "schema": { + "title": "Host", + "type": "string", + "description": "It will be ignored for non-admin user." + }, + "name": "host", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Flavor Id", + "type": "string" + }, + "name": "flavor_id", + "in": "query" + }, + { + "description": "UUID of server.", + "required": false, + "schema": { + "title": "Uuid", + "type": "string", + "description": "UUID of server." + }, + "name": "uuid", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtListServersResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestMessage" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/extension/recycle_servers": { + "get": { + "tags": [ + "Extension" + ], + "summary": "List Recycle Servers", + "description": "\nList Recycle Servers.\n\n*Notes*:\n- The `updated_at` of **sort_keys** is used as `deleted_at`.\n- The `name` is to support for fuzzy queries.\n", + "operationId": "list_recycle_servers_api_v1_extension_recycle_servers_get", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "exclusiveMinimum": 0.0, + "type": "integer" + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Marker", + "type": "string" + }, + "name": "marker", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtSortDir" + }, + "name": "sort_dirs", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtRecycleServerSortKey" + } + }, + "name": "sort_keys", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "All Projects", + "type": "boolean" + }, + "name": "all_projects", + "in": "query" + }, + { + "description": "Only works when the all_projects filter is also specified.", + "required": false, + "schema": { + "title": "Project Id", + "type": "string", + "description": "Only works when the all_projects filter is also specified." + }, + "name": "project_id", + "in": "query" + }, + { + "description": "Only works when the all_projects filter is also specified.", + "required": false, + "schema": { + "title": "Project Name", + "type": "string", + "description": "Only works when the all_projects filter is also specified." + }, + "name": "project_name", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Name", + "type": "string" + }, + "name": "name", + "in": "query" + }, + { + "description": "UUID of recycle server.", + "required": false, + "schema": { + "title": "Uuid", + "type": "string", + "description": "UUID of recycle server." + }, + "name": "uuid", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtListRecycleServersResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestMessage" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/extension/volumes": { + "get": { + "tags": [ + "Extension" + ], + "summary": "List Volumes", + "description": "List Volumes.", + "operationId": "list_volumes_api_v1_extension_volumes_get", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "exclusiveMinimum": 0.0, + "type": "integer" + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Marker", + "type": "string" + }, + "name": "marker", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtSortDir" + }, + "name": "sort_dirs", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtVolumeSortKey" + } + }, + "name": "sort_keys", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "All Projects", + "type": "boolean" + }, + "name": "all_projects", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Project Id", + "type": "string" + }, + "name": "project_id", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Name", + "type": "string" + }, + "name": "name", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Multiattach", + "type": "boolean" + }, + "name": "multiattach", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtVolumeStatus" + }, + "name": "status", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Bootable", + "type": "boolean" + }, + "name": "bootable", + "in": "query" + }, + { + "description": "UUID of volume.", + "required": false, + "schema": { + "title": "Uuid", + "type": "array", + "items": { + "type": "string" + }, + "description": "UUID of volume." + }, + "name": "uuid", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtListVolumesResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/extension/volume_snapshots": { + "get": { + "tags": [ + "Extension" + ], + "summary": "List Volume Snapshots", + "description": "List Volume Snapshots.", + "operationId": "list_volume_snapshots_api_v1_extension_volume_snapshots_get", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "exclusiveMinimum": 0.0, + "type": "integer" + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Marker", + "type": "string" + }, + "name": "marker", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtSortDir" + }, + "name": "sort_dirs", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtVolumeSnapshotSortKey" + } + }, + "name": "sort_keys", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "All Projects", + "type": "boolean" + }, + "name": "all_projects", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Project Id", + "type": "string" + }, + "name": "project_id", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Name", + "type": "string" + }, + "name": "name", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtVolumeSnapshotStatus" + }, + "name": "status", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Volume Id", + "type": "string" + }, + "name": "volume_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtListVolumeSnapshotsResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/extension/ports": { + "get": { + "tags": [ + "Extension" + ], + "summary": "List Ports", + "description": "List Ports.", + "operationId": "list_ports_api_v1_extension_ports_get", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "exclusiveMinimum": 0.0, + "type": "integer" + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Marker", + "type": "string" + }, + "name": "marker", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtSortDir" + }, + "name": "sort_dirs", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtPortSortKey" + } + }, + "name": "sort_keys", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "All Projects", + "type": "boolean" + }, + "name": "all_projects", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Project Id", + "type": "string" + }, + "name": "project_id", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Name", + "type": "string" + }, + "name": "name", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/ExtPortStatus" + }, + "name": "status", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Network Name", + "type": "string" + }, + "name": "network_name", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Network Id", + "type": "string" + }, + "name": "network_id", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Device Id", + "type": "string" + }, + "name": "device_id", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtPortDeviceOwner" + } + }, + "name": "device_owner", + "in": "query" + }, + { + "description": "UUID of port.", + "required": false, + "schema": { + "title": "Uuid", + "type": "array", + "items": { + "type": "string" + }, + "description": "UUID of port." + }, + "name": "uuid", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtListPortsResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/extension/compute-services": { + "get": { + "tags": [ + "Extension" + ], + "summary": "Compute Services", + "description": "List compute services.", + "operationId": "compute_services_api_v1_extension_compute_services_get", + "parameters": [ + { + "required": false, + "schema": { + "title": "Binary", + "type": "string" + }, + "name": "binary", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Host", + "type": "string" + }, + "name": "host", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtListComputeServicesResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/contrib/keystone_endpoints": { + "get": { + "tags": [ + "Contrib" + ], + "summary": "List Keystone Endpoints", + "description": "List Keystone Endpoints", + "operationId": "list_keystone_endpoints_api_v1_contrib_keystone_endpoints_get", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Response 200 List Keystone Endpoints Api V1 Contrib Keystone Endpoints Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/ContribListKeystoneEndpointsResponseModel" + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + } + } + } + }, + "/api/v1/contrib/domains": { + "get": { + "tags": [ + "Contrib" + ], + "summary": "List Domains", + "description": "List Domains", + "operationId": "list_domains_api_v1_contrib_domains_get", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Response 200 List Domains Api V1 Contrib Domains Get", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + } + } + } + }, + "/api/v1/contrib/regions": { + "get": { + "tags": [ + "Contrib" + ], + "summary": "List Regions", + "description": "List Regions", + "operationId": "list_regions_api_v1_contrib_regions_get", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Response 200 List Regions Api V1 Contrib Regions Get", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + } + } + } + }, + "/api/v1/policies": { + "get": { + "tags": [ + "Policy" + ], + "summary": "List Policies", + "description": "List policies and permissions", + "operationId": "list_policies_api_v1_policies_get", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Policies" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + } + } + } + }, + "/api/v1/policies/check": { + "post": { + "tags": [ + "Policy" + ], + "summary": "Check Policies", + "description": "Check policies permissions", + "operationId": "check_policies_api_v1_policies_check_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PoliciesRules" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Policies" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/setting/{key}": { + "get": { + "tags": [ + "Setting" + ], + "summary": "Show Setting", + "description": "Get a setting item.", + "operationId": "show_setting_api_v1_setting__key__get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Key", + "type": "string" + }, + "name": "key", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Setting" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Setting" + ], + "summary": "Reset Setting", + "description": "Reset a setting item to default", + "operationId": "reset_setting_api_v1_setting__key__delete", + "parameters": [ + { + "required": true, + "schema": { + "title": "Key", + "type": "string" + }, + "name": "key", + "in": "path" + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundMessage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/setting": { + "put": { + "tags": [ + "Setting" + ], + "summary": "Update Setting", + "description": "Update a setting item.", + "operationId": "update_setting_api_v1_setting_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSetting" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Setting" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenMessage" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundMessage" + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnprocessableEntityMessage" + } + } + } + } + } + } + }, + "/api/v1/settings": { + "get": { + "tags": [ + "Setting" + ], + "summary": "List Settings", + "description": "Get all settings.", + "operationId": "list_settings_api_v1_settings_get", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Settings" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedMessage" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "BadRequestMessage": { + "title": "BadRequestMessage", + "required": [ + "detail" + ], + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "string" + } + } + }, + "ContribListKeystoneEndpointsResponseModel": { + "title": "ContribListKeystoneEndpointsResponseModel", + "required": [ + "region_name", + "url" + ], + "type": "object", + "properties": { + "region_name": { + "title": "Region Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + } + }, + "Credential": { + "title": "Credential", + "required": [ + "region", + "domain", + "username", + "password" + ], + "type": "object", + "properties": { + "region": { + "title": "Region", + "type": "string" + }, + "domain": { + "title": "Domain", + "type": "string" + }, + "username": { + "title": "Username", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" + } + }, + "example": { + "region": "RegionOne", + "username": "admin", + "domain": "default", + "password": "admin" + } + }, + "Domain": { + "title": "Domain", + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + } + } + }, + "ExtFlavor": { + "title": "ExtFlavor", + "type": "object", + "properties": { + "ephemeral": { + "title": "Ephemeral", + "type": "integer" + }, + "ram": { + "title": "Ram", + "type": "integer" + }, + "original_name": { + "title": "Original Name", + "type": "string" + }, + "vcpus": { + "title": "Vcpus", + "type": "integer" + }, + "extra_specs": { + "title": "Extra Specs", + "type": "object" + }, + "swap": { + "title": "Swap", + "type": "integer" + }, + "disk": { + "title": "Disk", + "type": "integer" + } + } + }, + "ExtListComputeServicesBaseResponse": { + "title": "ExtListComputeServicesBaseResponse", + "required": [ + "binary", + "host", + "status" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "binary": { + "title": "Binary", + "type": "string" + }, + "disabled_reason": { + "title": "Disabled Reason", + "type": "string" + }, + "host": { + "title": "Host", + "type": "string" + }, + "state": { + "title": "State", + "type": "string" + }, + "status": { + "title": "Status", + "type": "string" + }, + "updated_at": { + "title": "Updated At", + "type": "string" + }, + "forced_down": { + "title": "Forced Down", + "type": "boolean" + }, + "zone": { + "title": "Zone", + "type": "string" + } + } + }, + "ExtListComputeServicesResponse": { + "title": "ExtListComputeServicesResponse", + "required": [ + "services" + ], + "type": "object", + "properties": { + "services": { + "title": "Services", + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtListComputeServicesBaseResponse" + } + } + } + }, + "ExtListPortsBaseResponse": { + "title": "ExtListPortsBaseResponse", + "required": [ + "id", + "origin_data" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "origin_data": { + "title": "Origin Data", + "type": "object", + "description": "The origin_data is the same like the response of https://docs.openstack.org/api-ref/network/v2/index.html?expanded=list-ports-detail#list-ports" + }, + "server_name": { + "title": "Server Name", + "type": "string" + }, + "network_name": { + "title": "Network Name", + "type": "string" + }, + "ipv4": { + "title": "Ipv4", + "type": "array", + "items": {} + }, + "ipv6": { + "title": "Ipv6", + "type": "array", + "items": {} + }, + "name": { + "title": "Name", + "type": "string", + "description": "Will be removed, please use origin_data[name]", + "deprecated": true + }, + "mac_address": { + "title": "Mac Address", + "type": "string", + "description": "Will be removed, please use origin_data[mac_address]", + "deprecated": true + }, + "project_id": { + "title": "Project Id", + "type": "string", + "description": "Will be removed, please use origin_data[project_id]", + "deprecated": true + }, + "device_owner": { + "title": "Device Owner", + "type": "string", + "description": "Will be removed, please use origin_data[device_owner]", + "deprecated": true + }, + "device_id": { + "title": "Device Id", + "type": "string", + "description": "Will be removed, please use origin_data[device_id]", + "deprecated": true + }, + "status": { + "title": "Status", + "type": "string", + "description": "Will be removed, please use origin_data[status]", + "deprecated": true + }, + "created_at": { + "title": "Created At", + "type": "string", + "description": "Will be removed, please use origin_data[created_at]", + "deprecated": true + }, + "network_id": { + "title": "Network Id", + "type": "string", + "description": "Will be removed, please use origin_data[network_id]", + "deprecated": true + }, + "binding_vnic_type": { + "title": "Binding Vnic Type", + "type": "string", + "description": "Will be removed, please use origin_data[binding:vnic_type]", + "deprecated": true + }, + "description": { + "title": "Description", + "type": "string", + "description": "Will be removed, please use origin_data[description]", + "deprecated": true + }, + "port_security_enabled": { + "title": "Port Security Enabled", + "type": "boolean", + "description": "Will be removed, please use origin_data[port_security_enabled]", + "deprecated": true + }, + "qos_policy_id": { + "title": "Qos Policy Id", + "type": "string", + "description": "Will be removed, please use origin_data[qos_policy_id]", + "deprecated": true + }, + "fixed_ips": { + "title": "Fixed Ips", + "type": "array", + "items": {}, + "description": "Will be removed, please use origin_data[fixed_ips]", + "deprecated": true + } + } + }, + "ExtListPortsResponse": { + "title": "ExtListPortsResponse", + "required": [ + "ports" + ], + "type": "object", + "properties": { + "count": { + "title": "Count", + "type": "integer", + "default": 0 + }, + "ports": { + "title": "Ports", + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtListPortsBaseResponse" + } + } + } + }, + "ExtListRecycleServersBaseResponse": { + "title": "ExtListRecycleServersBaseResponse", + "required": [ + "id", + "origin_data", + "reclaim_timestamp" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid4" + }, + "origin_data": { + "title": "Origin Data", + "type": "object", + "description": "The origin_data is the same like the response of https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detailed-detail#list-servers-detailed" + }, + "project_name": { + "title": "Project Name", + "type": "string" + }, + "image": { + "title": "Image", + "type": "string", + "format": "uuid4" + }, + "image_name": { + "title": "Image Name", + "type": "string" + }, + "image_os_distro": { + "title": "Image Os Distro", + "type": "string" + }, + "fixed_addresses": { + "title": "Fixed Addresses", + "type": "array", + "items": {} + }, + "floating_addresses": { + "title": "Floating Addresses", + "type": "array", + "items": {} + }, + "deleted_at": { + "title": "Deleted At", + "type": "string" + }, + "reclaim_timestamp": { + "title": "Reclaim Timestamp", + "type": "number" + }, + "name": { + "title": "Name", + "type": "string", + "description": "Will be removed, please use origin_data[name]", + "deprecated": true + }, + "project_id": { + "title": "Project Id", + "type": "string", + "description": "Will be removed, please use origin_data[tenant_id]", + "deprecated": true + }, + "host": { + "title": "Host", + "type": "string", + "description": "Will be removed, please use origin_data[OS-EXT-SRV-ATTR:host]", + "deprecated": true + }, + "hostname": { + "title": "Hostname", + "type": "string", + "description": "Will be removed, please use origin_data[OS-EXT-SRV-ATTR:hostname]", + "deprecated": true + }, + "flavor": { + "title": "Flavor", + "type": "string", + "description": "Will be removed, please use origin_data[flavor][original_name]", + "deprecated": true + }, + "flavor_info": { + "title": "Flavor Info", + "allOf": [ + { + "$ref": "#/components/schemas/ExtFlavor" + } + ], + "description": "Will be removed, please use origin_data[flavor]", + "deprecated": true + }, + "status": { + "title": "Status", + "type": "string", + "description": "Will be removed, please use origin_data[status]", + "deprecated": true + } + } + }, + "ExtListRecycleServersResponse": { + "title": "ExtListRecycleServersResponse", + "required": [ + "recycle_servers" + ], + "type": "object", + "properties": { + "recycle_servers": { + "title": "Recycle Servers", + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtListRecycleServersBaseResponse" + } + } + } + }, + "ExtListServersBaseResponse": { + "title": "ExtListServersBaseResponse", + "required": [ + "id", + "origin_data" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid4" + }, + "origin_data": { + "title": "Origin Data", + "type": "object", + "description": "The origin_data is the same like the response of https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detailed-detail#list-servers-detailed" + }, + "project_name": { + "title": "Project Name", + "type": "string" + }, + "image": { + "title": "Image", + "type": "string", + "format": "uuid4" + }, + "image_name": { + "title": "Image Name", + "type": "string" + }, + "image_os_distro": { + "title": "Image Os Distro", + "type": "string" + }, + "fixed_addresses": { + "title": "Fixed Addresses", + "type": "array", + "items": {} + }, + "floating_addresses": { + "title": "Floating Addresses", + "type": "array", + "items": {} + }, + "name": { + "title": "Name", + "type": "string", + "description": "Will be removed, please use origin_data[name]", + "deprecated": true + }, + "project_id": { + "title": "Project Id", + "type": "string", + "description": "Will be removed, please use origin_data[tenant_id]", + "deprecated": true + }, + "host": { + "title": "Host", + "type": "string", + "description": "Will be removed, please use origin_data[OS-EXT-SRV-ATTR:host]", + "deprecated": true + }, + "hostname": { + "title": "Hostname", + "type": "string", + "description": "Will be removed, please use origin_data[OS-EXT-SRV-ATTR:hostname]", + "deprecated": true + }, + "flavor": { + "title": "Flavor", + "type": "string", + "description": "Will be removed, please use origin_data[flavor][original_name]", + "deprecated": true + }, + "flavor_info": { + "title": "Flavor Info", + "allOf": [ + { + "$ref": "#/components/schemas/ExtFlavor" + } + ], + "description": "Will be removed, please use origin_data[flavor]", + "deprecated": true + }, + "status": { + "title": "Status", + "type": "string", + "description": "Will be removed, please use origin_data[status]", + "deprecated": true + }, + "locked": { + "title": "Locked", + "type": "boolean", + "description": "Will be removed, please use origin_data[locked]", + "deprecated": true + }, + "created_at": { + "title": "Created At", + "type": "string", + "description": "Will be removed, please use origin_data[created]", + "deprecated": true + }, + "task_state": { + "title": "Task State", + "type": "string", + "description": "Will be removed, please use origin_data[OS-EXT-STS:task_state]", + "deprecated": true + }, + "vm_state": { + "title": "Vm State", + "type": "string", + "description": "Will be removed, please use origin_data[OS-EXT-STS:vm_state]", + "deprecated": true + }, + "power_state": { + "title": "Power State", + "type": "integer", + "description": "Will be removed, please use origin_data[OS-EXT-STS:power_state]", + "deprecated": true + }, + "root_device_name": { + "title": "Root Device Name", + "type": "string", + "description": "Will be removed, please use origin_data[OS-EXT-SRV-ATTR:root_device_name]", + "deprecated": true + }, + "metadata": { + "title": "Metadata", + "type": "object", + "description": "Will be removed, please use origin_data[metadata]", + "deprecated": true + } + } + }, + "ExtListServersResponse": { + "title": "ExtListServersResponse", + "required": [ + "servers" + ], + "type": "object", + "properties": { + "servers": { + "title": "Servers", + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtListServersBaseResponse" + } + } + } + }, + "ExtListVolumeSnapshotsBaseResponse": { + "title": "ExtListVolumeSnapshotsBaseResponse", + "required": [ + "id", + "origin_data" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "origin_data": { + "title": "Origin Data", + "type": "object", + "description": "The origin_data is the same like the response of https://docs.openstack.org/api-ref/block-storage/v3/index.html?expanded=list-snapshots-and-details-detail#list-snapshots-and-details" + }, + "project_name": { + "title": "Project Name", + "type": "string" + }, + "host": { + "title": "Host", + "type": "string" + }, + "volume_name": { + "title": "Volume Name", + "type": "string" + }, + "child_volumes": { + "title": "Child Volumes", + "type": "array", + "items": {} + }, + "name": { + "title": "Name", + "type": "string", + "description": "Will be removed, please use origin_data[name]", + "deprecated": true + }, + "project_id": { + "title": "Project Id", + "type": "string", + "description": "Will be removed, please use origin_data[os-extended-snapshot-attributes:project_id]", + "deprecated": true + }, + "size": { + "title": "Size", + "type": "integer", + "description": "Will be removed, please use origin_data[size]", + "deprecated": true + }, + "status": { + "title": "Status", + "type": "string", + "description": "Will be removed, please use origin_data[status]", + "deprecated": true + }, + "volume_id": { + "title": "Volume Id", + "type": "string", + "description": "Will be removed, please use origin_data[volume_id]", + "deprecated": true + }, + "created_at": { + "title": "Created At", + "type": "string", + "description": "Will be removed, please use origin_data[created_at]", + "deprecated": true + }, + "metadata": { + "title": "Metadata", + "type": "object", + "description": "Will be removed, please use origin_data[metadata]", + "deprecated": true + } + } + }, + "ExtListVolumeSnapshotsResponse": { + "title": "ExtListVolumeSnapshotsResponse", + "required": [ + "volume_snapshots" + ], + "type": "object", + "properties": { + "count": { + "title": "Count", + "type": "integer", + "default": 0 + }, + "volume_snapshots": { + "title": "Volume Snapshots", + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtListVolumeSnapshotsBaseResponse" + } + } + } + }, + "ExtListVolumesBaseResponse": { + "title": "ExtListVolumesBaseResponse", + "required": [ + "id", + "origin_data" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid4" + }, + "origin_data": { + "title": "Origin Data", + "type": "object", + "description": "The origin_data is the same like the response of https://docs.openstack.org/api-ref/block-storage/v3/index.html?expanded=list-accessible-volumes-with-details-detail#list-accessible-volumes-with-details" + }, + "project_name": { + "title": "Project Name", + "type": "string" + }, + "attachments": { + "title": "Attachments", + "type": "array", + "items": { + "$ref": "#/components/schemas/VolumeAttachment" + } + }, + "name": { + "title": "Name", + "type": "string", + "description": "Will be removed, please use origin_data[name]", + "deprecated": true + }, + "project_id": { + "title": "Project Id", + "type": "string", + "description": "Will be removed, please use origin_data[os-vol-tenant-attr:tenant_id]", + "deprecated": true + }, + "host": { + "title": "Host", + "type": "string", + "description": "Will be removed, please use origin_data[os-vol-host-attr:host]", + "deprecated": true + }, + "snapshot_id": { + "title": "Snapshot Id", + "type": "string", + "description": "Will be removed, please use origin_data[snapshot_id]", + "deprecated": true + }, + "source_volid": { + "title": "Source Volid", + "type": "string", + "description": "Will be removed, please use origin_data[source_volid]", + "deprecated": true + }, + "size": { + "title": "Size", + "type": "integer", + "description": "Will be removed, please use origin_data[size]", + "deprecated": true + }, + "status": { + "title": "Status", + "type": "string", + "description": "Will be removed, please use origin_data[status]", + "deprecated": true + }, + "volume_type": { + "title": "Volume Type", + "type": "string", + "description": "Will be removed, please use origin_data[volume_type]", + "deprecated": true + }, + "encrypted": { + "title": "Encrypted", + "type": "boolean", + "description": "Will be removed, please use origin_data[encrypted]", + "deprecated": true + }, + "bootable": { + "title": "Bootable", + "type": "string", + "description": "Will be removed, please use origin_data[bootable]", + "deprecated": true + }, + "multiattach": { + "title": "Multiattach", + "type": "boolean", + "description": "Will be removed, please use origin_data[multiattach]", + "deprecated": true + }, + "availability_zone": { + "title": "Availability Zone", + "type": "string", + "description": "Will be removed, please use origin_data[availability_zone]", + "deprecated": true + }, + "created_at": { + "title": "Created At", + "type": "string", + "description": "Will be removed, please use origin_data[created_at]", + "deprecated": true + } + } + }, + "ExtListVolumesResponse": { + "title": "ExtListVolumesResponse", + "required": [ + "volumes" + ], + "type": "object", + "properties": { + "count": { + "title": "Count", + "type": "integer", + "default": 0 + }, + "volumes": { + "title": "Volumes", + "type": "array", + "items": { + "$ref": "#/components/schemas/ExtListVolumesBaseResponse" + } + } + } + }, + "ExtPortDeviceOwner": { + "title": "ExtPortDeviceOwner", + "enum": [ + "", + "compute:nova", + "network:dhcp", + "network:floatingip", + "network:router_gateway", + "network:router_ha_interface", + "network:ha_router_replicated_interface" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtPortSortKey": { + "title": "ExtPortSortKey", + "enum": [ + "id", + "name", + "mac_address", + "status", + "project_id" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtPortStatus": { + "title": "ExtPortStatus", + "enum": [ + "ACTIVE", + "DOWN", + "BUILD", + "ERROR", + "N/A" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtRecycleServerSortKey": { + "title": "ExtRecycleServerSortKey", + "enum": [ + "uuid", + "display_name", + "updated_at", + "project_id" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtServerSortKey": { + "title": "ExtServerSortKey", + "enum": [ + "uuid", + "display_name", + "vm_state", + "locked", + "created_at", + "host", + "project_id" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtServerStatus": { + "title": "ExtServerStatus", + "enum": [ + "ACTIVE", + "BUILD", + "ERROR", + "HARD_REBOOT", + "MIGRATING", + "PAUSED", + "REBOOT", + "REBUILD", + "RESCUE", + "RESIZE", + "SHELVED", + "SHELVED_OFFLOADED", + "SHUTOFF", + "SOFT_DELETED", + "SUSPENDED", + "UNKNOWN" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtSortDir": { + "title": "ExtSortDir", + "enum": [ + "desc", + "asc" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtVolumeSnapshotSortKey": { + "title": "ExtVolumeSnapshotSortKey", + "enum": [ + "id", + "name", + "status", + "created_at" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtVolumeSnapshotStatus": { + "title": "ExtVolumeSnapshotStatus", + "enum": [ + "CREATING", + "AVAILABLE", + "DELETING", + "ERROR", + "ERROR_DELETING" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtVolumeSortKey": { + "title": "ExtVolumeSortKey", + "enum": [ + "id", + "name", + "size", + "status", + "bootable", + "created_at" + ], + "type": "string", + "description": "An enumeration." + }, + "ExtVolumeStatus": { + "title": "ExtVolumeStatus", + "enum": [ + "creating", + "available", + "reserved", + "attaching", + "detaching", + "in-use", + "maintenance", + "deleting", + "awaiting-transfer", + "error", + "error_deleting", + "backing-up", + "restoring-backup", + "error_backing-up", + "error_restoring", + "error_extending", + "downloading", + "uploading", + "retyping", + "extending" + ], + "type": "string", + "description": "An enumeration." + }, + "ForbiddenMessage": { + "title": "ForbiddenMessage", + "required": [ + "detail" + ], + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + }, + "InternalServerErrorMessage": { + "title": "InternalServerErrorMessage", + "required": [ + "detail" + ], + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "string" + } + } + }, + "License": { + "title": "License", + "required": [ + "name", + "summary", + "macs", + "features", + "start", + "end" + ], + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "summary": { + "title": "Summary", + "type": "string" + }, + "macs": { + "title": "Macs", + "type": "array", + "items": { + "type": "string" + } + }, + "features": { + "title": "Features", + "type": "array", + "items": { + "type": "object" + } + }, + "start": { + "title": "Start", + "type": "string" + }, + "end": { + "title": "End", + "type": "string" + } + } + }, + "NotFoundMessage": { + "title": "NotFoundMessage", + "required": [ + "detail" + ], + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "string" + } + } + }, + "OK": { + "title": "OK", + "required": [ + "message" + ], + "type": "object", + "properties": { + "code": { + "title": "Code", + "type": "integer", + "default": 200 + }, + "message": { + "title": "Message", + "type": "string" + }, + "title": { + "title": "Title", + "type": "string", + "default": "OK" + } + } + }, + "Policies": { + "title": "Policies", + "required": [ + "policies" + ], + "type": "object", + "properties": { + "policies": { + "title": "Policies", + "type": "array", + "items": { + "$ref": "#/components/schemas/Policy" + } + } + } + }, + "PoliciesRules": { + "title": "PoliciesRules", + "required": [ + "rules" + ], + "type": "object", + "properties": { + "rules": { + "title": "Rules", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Policy": { + "title": "Policy", + "required": [ + "rule", + "allowed" + ], + "type": "object", + "properties": { + "rule": { + "title": "Rule", + "type": "string" + }, + "allowed": { + "title": "Allowed", + "type": "boolean" + } + } + }, + "Profile": { + "title": "Profile", + "required": [ + "keystone_token", + "region", + "project", + "user", + "roles", + "keystone_token_exp", + "exp", + "uuid", + "version" + ], + "type": "object", + "properties": { + "keystone_token": { + "title": "Keystone Token", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + }, + "project": { + "$ref": "#/components/schemas/Project" + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "roles": { + "title": "Roles", + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "keystone_token_exp": { + "title": "Keystone Token Exp", + "type": "string" + }, + "base_roles": { + "title": "Base Roles", + "type": "array", + "items": { + "type": "string" + } + }, + "base_domains": { + "title": "Base Domains", + "type": "array", + "items": { + "type": "string" + } + }, + "endpoints": { + "title": "Endpoints", + "type": "object" + }, + "projects": { + "title": "Projects", + "type": "object" + }, + "exp": { + "title": "Exp", + "type": "integer" + }, + "uuid": { + "title": "Uuid", + "type": "string" + }, + "version": { + "title": "Version", + "type": "string" + }, + "license": { + "$ref": "#/components/schemas/License" + }, + "currency": { + "title": "Currency", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "Project": { + "title": "Project", + "required": [ + "id", + "name", + "domain" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "domain": { + "$ref": "#/components/schemas/Domain" + } + } + }, + "Role": { + "title": "Role", + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + } + } + }, + "Setting": { + "title": "Setting", + "required": [ + "key", + "restart_service" + ], + "type": "object", + "properties": { + "key": { + "title": "Key", + "type": "string" + }, + "value": { + "title": "Value" + }, + "hidden": { + "title": "Hidden", + "type": "boolean" + }, + "restart_service": { + "title": "Restart Service", + "type": "boolean" + } + } + }, + "Settings": { + "title": "Settings", + "required": [ + "settings" + ], + "type": "object", + "properties": { + "settings": { + "title": "Settings", + "type": "array", + "items": { + "$ref": "#/components/schemas/Setting" + } + } + } + }, + "UnauthorizedMessage": { + "title": "UnauthorizedMessage", + "required": [ + "detail" + ], + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "string" + } + } + }, + "UnprocessableEntityMessage": { + "title": "UnprocessableEntityMessage", + "required": [ + "detail" + ], + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "string" + } + } + }, + "UpdateSetting": { + "title": "UpdateSetting", + "required": [ + "key" + ], + "type": "object", + "properties": { + "key": { + "title": "Key", + "type": "string" + }, + "value": { + "title": "Value" + } + } + }, + "User": { + "title": "User", + "required": [ + "id", + "name", + "domain" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "domain": { + "$ref": "#/components/schemas/Domain" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "VolumeAttachment": { + "title": "VolumeAttachment", + "required": [ + "id" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "device": { + "title": "Device", + "type": "string" + }, + "server_id": { + "title": "Server Id", + "type": "string" + }, + "server_name": { + "title": "Server Name", + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/etc/.gitignore b/etc/.gitignore new file mode 100644 index 0000000..1bf35e9 --- /dev/null +++ b/etc/.gitignore @@ -0,0 +1,2 @@ +*.yaml +proxy.conf diff --git a/etc/gunicorn.py b/etc/gunicorn.py new file mode 100644 index 0000000..72afd32 --- /dev/null +++ b/etc/gunicorn.py @@ -0,0 +1,8 @@ +bind = ['0.0.0.0:28000'] +workers = 2 +worker_class = "uvicorn.workers.UvicornWorker" +timeout = 3600 +keepalive = 5 +reuse_port = True +proc_name = "skyline-apiserver" +log_level = "debug" diff --git a/libs/skyline-log/.flake8 b/libs/skyline-log/.flake8 new file mode 100644 index 0000000..e5582fa --- /dev/null +++ b/libs/skyline-log/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 99 +max-doc-length = 99 +show-source = True +extend-ignore = E203 diff --git a/libs/skyline-log/LICENSE b/libs/skyline-log/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/libs/skyline-log/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/libs/skyline-log/Makefile b/libs/skyline-log/Makefile new file mode 100644 index 0000000..d42f081 --- /dev/null +++ b/libs/skyline-log/Makefile @@ -0,0 +1,35 @@ +PYTHON ?= python3 +SOURCES := src +TESTS := tests + + +.PHONY: venv +venv: + poetry env use $(PYTHON) + + +.PHONY: install +install: venv + poetry run pip install -U pip + poetry run pip install -U setuptools + poetry install -vvv + + +.PHONY: build +build: + poetry build + + +.PHONY: lint +lint: + poetry run mypy --no-incremental $(SOURCES) + poetry run isort --check-only --diff $(SOURCES) $(TESTS) + poetry run black --check --diff --color $(SOURCES) $(TESTS) + poetry run flake8 $(SOURCES) $(TESTS) + + +.PHONY: fmt +fmt: + poetry run isort $(SOURCES) $(TESTS) + poetry run black $(SOURCES) $(TESTS) + poetry run add-trailing-comma --py36-plus --exit-zero-even-if-changed `find $(SOURCES) $(TESTS) -name '*.py'` diff --git a/libs/skyline-log/mypy.ini b/libs/skyline-log/mypy.ini new file mode 100644 index 0000000..baa39f6 --- /dev/null +++ b/libs/skyline-log/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +show_error_codes = true +show_error_context = true diff --git a/libs/skyline-log/poetry.lock b/libs/skyline-log/poetry.lock new file mode 100644 index 0000000..465923c --- /dev/null +++ b/libs/skyline-log/poetry.lock @@ -0,0 +1,502 @@ +[[package]] +name = "add-trailing-comma" +version = "2.1.0" +description = "Automatically add trailing commas to calls and literals" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +tokenize-rt = ">=3.0.1" + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +name = "click" +version = "8.0.0" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.8.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "loguru" +version = "0.5.3" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy" +version = "0.812" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.4" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "regex" +version = "2021.4.4" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "tokenize-rt" +version = "4.1.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "win32-setctime" +version = "1.0.3" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "514eb800914e4b19c4fe26cc33512163ef7f38e7d422059c8c941b71e830def0" + +[metadata.files] +add-trailing-comma = [ + {file = "add_trailing_comma-2.1.0-py2.py3-none-any.whl", hash = "sha256:f462403aa2e997e20855708edb57536d1d3310d5c5fac7e80542578eb47fdb10"}, + {file = "add_trailing_comma-2.1.0.tar.gz", hash = "sha256:f9864ffbc12ea4e54916a356d57341ab58f612867c2ad453339c51004807e8ce"}, +] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +click = [ + {file = "click-8.0.0-py3-none-any.whl", hash = "sha256:e90e62ced43dc8105fb9a26d62f0d9340b5c8db053a814e25d95c19873ae87db"}, + {file = "click-8.0.0.tar.gz", hash = "sha256:7d8c289ee437bcb0316820ccee14aefcb056e58d31830ecab8e47eda6540e136"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, +] +loguru = [ + {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, + {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy = [ + {file = "mypy-0.812-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49"}, + {file = "mypy-0.812-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c"}, + {file = "mypy-0.812-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521"}, + {file = "mypy-0.812-cp35-cp35m-win_amd64.whl", hash = "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"}, + {file = "mypy-0.812-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a"}, + {file = "mypy-0.812-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c"}, + {file = "mypy-0.812-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6"}, + {file = "mypy-0.812-cp36-cp36m-win_amd64.whl", hash = "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064"}, + {file = "mypy-0.812-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56"}, + {file = "mypy-0.812-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8"}, + {file = "mypy-0.812-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7"}, + {file = "mypy-0.812-cp37-cp37m-win_amd64.whl", hash = "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564"}, + {file = "mypy-0.812-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506"}, + {file = "mypy-0.812-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5"}, + {file = "mypy-0.812-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66"}, + {file = "mypy-0.812-cp38-cp38-win_amd64.whl", hash = "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e"}, + {file = "mypy-0.812-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a"}, + {file = "mypy-0.812-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a"}, + {file = "mypy-0.812-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97"}, + {file = "mypy-0.812-cp39-cp39-win_amd64.whl", hash = "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df"}, + {file = "mypy-0.812-py3-none-any.whl", hash = "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4"}, + {file = "mypy-0.812.tar.gz", hash = "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, +] +regex = [ + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, +] +tokenize-rt = [ + {file = "tokenize_rt-4.1.0-py2.py3-none-any.whl", hash = "sha256:b37251fa28c21e8cce2e42f7769a35fba2dd2ecafb297208f9a9a8add3ca7793"}, + {file = "tokenize_rt-4.1.0.tar.gz", hash = "sha256:ab339b5ff829eb5e198590477f9c03c84e762b3e455e74c018956e7e326cbc70"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +win32-setctime = [ + {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, + {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, +] diff --git a/libs/skyline-log/poetry.toml b/libs/skyline-log/poetry.toml new file mode 100644 index 0000000..ab1033b --- /dev/null +++ b/libs/skyline-log/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/libs/skyline-log/pyproject.toml b/libs/skyline-log/pyproject.toml new file mode 100644 index 0000000..fbf3495 --- /dev/null +++ b/libs/skyline-log/pyproject.toml @@ -0,0 +1,54 @@ +[tool.poetry] +name = "skyline-log" +version = "0.1.0" +description = "" +license = "Apache-2.0" +authors = ["OpenStack "] + +[tool.poetry.dependencies] +python = "^3.8" +loguru = "*" + +[tool.poetry.dev-dependencies] +pytest = "*" +mypy = "*" +black = "^20.8b1" +isort = "*" +flake8 = "*" +add-trailing-comma = "*" + +[tool.black] +line-length = 98 +target-version = ['py38'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + )/ + | exclude.py +) +''' +verbos = true + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +line_length = 98 +reverse_relative = true +combine_as_imports = true + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/libs/skyline-log/src/skyline_log/__init__.py b/libs/skyline-log/src/skyline_log/__init__.py new file mode 100644 index 0000000..dfaf6be --- /dev/null +++ b/libs/skyline-log/src/skyline_log/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .log import LOG, setup + +__version__ = "0.1.0" + +__all__ = ("LOG", "setup") diff --git a/libs/skyline-log/src/skyline_log/log.py b/libs/skyline-log/src/skyline_log/log.py new file mode 100644 index 0000000..3364638 --- /dev/null +++ b/libs/skyline-log/src/skyline_log/log.py @@ -0,0 +1,82 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import inspect +import logging +from logging import LogRecord +from typing import Any, Optional + +import loguru +from loguru import logger + +LOG = loguru.logger + + +class InterceptHandler(logging.Handler): + def emit(self, record: LogRecord) -> None: + # Get corresponding Loguru level if it exists + level = getattr(logger.level(record.levelname), "name", record.levelno) + + # Find caller from where originated the logged message + frame, depth = getattr(inspect.currentframe(), "f_back", None), 1 + while frame and frame.f_code.co_filename == logging.__file__: + frame = frame.f_back + depth += 1 + + logger.opt(depth=depth, exception=record.exc_info).log( + level, + record.getMessage(), + ) + + +def setup( + sink: Any, + debug: bool = False, + colorize: bool = False, + level: Optional[str] = None, +) -> None: + if debug: + default_level = "DEBUG" + backtrace = True + diagnose = True + else: + default_level = "WARNING" + backtrace = False + diagnose = True + if level is None: + level = default_level + + LOG.remove() + LOG.add( + sink, + level=level, + format=( + "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} |" + " {name}:{function}:{line} -" + " {message}" + ), + filter=None, + colorize=colorize, + backtrace=backtrace, + diagnose=diagnose, + serialize=False, + enqueue=True, + catch=True, + ) + logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True) + + +__all__ = ("LOG", "setup") diff --git a/libs/skyline-log/tests/__init__.py b/libs/skyline-log/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/skyline-log/tests/test_log.py b/libs/skyline-log/tests/test_log.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/skyline-log/tests/test_skyline_log.py b/libs/skyline-log/tests/test_skyline_log.py new file mode 100644 index 0000000..c126c97 --- /dev/null +++ b/libs/skyline-log/tests/test_skyline_log.py @@ -0,0 +1,19 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from skyline_log import __version__ + + +def test_version(): + assert __version__ == "0.1.0" diff --git a/libs/skyline-policy-manager/.flake8 b/libs/skyline-policy-manager/.flake8 new file mode 100644 index 0000000..e5582fa --- /dev/null +++ b/libs/skyline-policy-manager/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 99 +max-doc-length = 99 +show-source = True +extend-ignore = E203 diff --git a/libs/skyline-policy-manager/LICENSE b/libs/skyline-policy-manager/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/libs/skyline-policy-manager/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/libs/skyline-policy-manager/Makefile b/libs/skyline-policy-manager/Makefile new file mode 100644 index 0000000..c337ec8 --- /dev/null +++ b/libs/skyline-policy-manager/Makefile @@ -0,0 +1,37 @@ +PYTHON ?= python3 +SOURCES := src +TESTS := tests +TOOLS := tools + + +.PHONY: venv +venv: + poetry env use $(PYTHON) + + +.PHONY: install +install: venv + poetry run pip install -U pip + poetry run pip install -U setuptools + poetry install -vvv + tools/post_install.sh + + +.PHONY: build +build: + poetry build + + +.PHONY: lint +lint: + poetry run mypy --no-incremental $(SOURCES) + poetry run isort --check-only --diff $(SOURCES) $(TESTS) $(TOOLS) + poetry run black --check --diff --color $(SOURCES) $(TESTS) $(TOOLS) + poetry run flake8 $(SOURCES) $(TESTS) $(TOOLS) + + +.PHONY: fmt +fmt: + poetry run isort $(SOURCES) $(TESTS) $(TOOLS) + poetry run black $(SOURCES) $(TESTS) $(TOOLS) + poetry run add-trailing-comma --py36-plus --exit-zero-even-if-changed `find $(SOURCES) $(TESTS) $(TOOLS) -name '*.py'` diff --git a/libs/skyline-policy-manager/mypy.ini b/libs/skyline-policy-manager/mypy.ini new file mode 100644 index 0000000..300653d --- /dev/null +++ b/libs/skyline-policy-manager/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +show_error_codes = true +show_error_context = true +mypy_path = ../skyline-log/src \ No newline at end of file diff --git a/libs/skyline-policy-manager/poetry.lock b/libs/skyline-policy-manager/poetry.lock new file mode 100644 index 0000000..e778421 --- /dev/null +++ b/libs/skyline-policy-manager/poetry.lock @@ -0,0 +1,2269 @@ +[[package]] +name = "add-trailing-comma" +version = "2.1.0" +description = "Automatically add trailing commas to calls and literals" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +tokenize-rt = ">=3.0.1" + +[[package]] +name = "alembic" +version = "1.6.2" +description = "A database migration tool for SQLAlchemy." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +Mako = "*" +python-dateutil = "*" +python-editor = ">=0.3" +SQLAlchemy = ">=1.3.0" + +[[package]] +name = "amqp" +version = "5.0.6" +description = "Low-level AMQP client for Python (fork of amqplib)." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +vine = "5.0.0" + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "argparse" +version = "1.4.0" +description = "Python command-line parsing library" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "beautifulsoup4" +version = "4.9.3" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +name = "cachetools" +version = "4.2.2" +description = "Extensible memoizing collections and decorators" +category = "dev" +optional = false +python-versions = "~=3.5" + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "click" +version = "8.0.0" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "debtcollector" +version = "2.2.0" +description = "A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +six = ">=1.10.0" +wrapt = ">=1.7.0" + +[[package]] +name = "decorator" +version = "5.0.9" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "dnspython" +version = "1.16.0" +description = "DNS toolkit" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"] +IDNA = ["idna (>=2.1)"] + +[[package]] +name = "eventlet" +version = "0.31.0" +description = "Highly concurrent networking library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +dnspython = ">=1.15.0,<2.0.0" +greenlet = ">=0.3" +six = ">=1.10.0" + +[[package]] +name = "extras" +version = "1.0.0" +description = "Useful extra bits for Python - things that shold be in the standard library" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "fasteners" +version = "0.16" +description = "A python package that provides useful locks." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[[package]] +name = "fixtures" +version = "3.0.0" +description = "Fixtures, reusable state for writing clean tests and more." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pbr = ">=0.11" +six = "*" +testtools = ">=0.9.22" + +[package.extras] +docs = ["docutils"] +test = ["mock"] + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "futurist" +version = "2.3.0" +description = "Useful additions to futures, from the future." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +six = ">=1.10.0" + +[[package]] +name = "greenlet" +version = "1.1.0" +description = "Lightweight in-process concurrent programming" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx"] + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "iso8601" +version = "0.1.14" +description = "Simple module to parse ISO 8601 dates" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.8.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "jinja2" +version = "3.0.1" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "keystoneauth1" +version = "4.3.1" +description = "Authentication Library for OpenStack Identity" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +iso8601 = ">=0.1.11" +os-service-types = ">=1.2.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +requests = ">=2.14.2" +six = ">=1.10.0" +stevedore = ">=1.20.0" + +[package.extras] +betamax = ["betamax (>=0.7.0)", "fixtures (>=3.0.0)", "mock (>=2.0.0)"] +kerberos = ["requests-kerberos (>=0.8.0)"] +oauth1 = ["oauthlib (>=0.6.2)"] +saml2 = ["lxml (>=4.2.0)"] +test = ["PyYAML (>=3.12)", "bandit (>=1.1.0,<1.6.0)", "betamax (>=0.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "flake8-docstrings (==0.2.1.post1)", "flake8-import-order (>=0.17.1)", "hacking (>=3.0.1,<3.1.0)", "lxml (>=4.2.0)", "oauthlib (>=0.6.2)", "oslo.config (>=5.2.0)", "oslo.utils (>=3.33.0)", "oslotest (>=3.2.0)", "pycodestyle (>=2.0.0,<2.6.0)", "reno (>=3.1.0)", "requests-kerberos (>=0.8.0)", "requests-mock (>=1.2.0)", "stestr (>=1.0.0)", "testresources (>=2.0.0)", "testtools (>=2.2.0)"] + +[[package]] +name = "kombu" +version = "5.1.0" +description = "Messaging library for Python." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +amqp = ">=5.0.6,<6.0.0" +vine = "*" + +[package.extras] +azureservicebus = ["azure-servicebus (>=7.0.0)"] +azurestoragequeues = ["azure-storage-queue"] +consul = ["python-consul (>=0.6.0)"] +librabbitmq = ["librabbitmq (>=1.5.2)"] +mongodb = ["pymongo (>=3.3.0)"] +msgpack = ["msgpack"] +pyro = ["pyro4"] +qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] +redis = ["redis (>=3.3.11)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +sqlalchemy = ["sqlalchemy"] +sqs = ["boto3 (>=1.4.4)", "pycurl (==7.43.0.2)", "urllib3 (<1.26)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] + +[[package]] +name = "linecache2" +version = "1.0.0" +description = "Backports of the linecache module" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "loguru" +version = "0.5.3" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] + +[[package]] +name = "logutils" +version = "0.3.5" +description = "Logging utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mako" +version = "1.1.4" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "msgpack" +version = "1.0.2" +description = "MessagePack (de)serializer." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "mypy" +version = "0.812" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "netaddr" +version = "0.8.0" +description = "A network address manipulation library for Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "netifaces" +version = "0.10.9" +description = "Portable network interface information." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "neutron-lib" +version = "2.11.0" +description = "Neutron shared routines and utilities" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +keystoneauth1 = ">=3.14.0" +netaddr = ">=0.7.18" +os-ken = ">=0.3.0" +os-traits = ">=0.9.0" +"oslo.concurrency" = ">=3.26.0" +"oslo.config" = ">=8.0.0" +"oslo.context" = ">=2.22.0" +"oslo.db" = ">=4.44.0" +"oslo.i18n" = ">=3.20.0" +"oslo.log" = ">=4.3.0" +"oslo.messaging" = ">=7.0.0" +"oslo.policy" = ">=3.6.2" +"oslo.serialization" = ">=2.25.0" +"oslo.service" = ">=1.24.0,<1.28.1 || >1.28.1" +"oslo.utils" = ">=4.5.0" +"oslo.versionedobjects" = ">=1.31.2" +osprofiler = ">=1.4.0" +pbr = ">=4.0.0" +pecan = ">=1.0.0,<1.0.2 || >1.0.2,<1.0.3 || >1.0.3,<1.0.4 || >1.0.4,<1.2 || >1.2" +setproctitle = ">=1.1.10" +SQLAlchemy = ">=1.2.0" +stevedore = ">=1.20.0" +WebOb = ">=1.7.1" + +[[package]] +name = "os-ken" +version = "1.4.0" +description = "A component-based software defined networking framework for OpenStack." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +eventlet = ">=0.22.0,<0.23.0 || >0.23.0" +msgpack = ">=0.5.0" +netaddr = ">=0.7.18" +"oslo.config" = ">=5.1.0" +ovs = ">=2.8.0" +pbr = ">=2.0.0" +Routes = ">=2.3.1" +six = ">=1.10.0" +tinyrpc = ">=0.6" +WebOb = ">=1.8.2" + +[[package]] +name = "os-service-types" +version = "1.7.0" +description = "Python library for consuming OpenStack sevice-types-authority data" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "os-traits" +version = "2.5.0" +description = "A library containing standardized trait strings" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "oslo.concurrency" +version = "4.4.0" +description = "Oslo Concurrency library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +fasteners = ">=0.7.0" +"oslo.config" = ">=5.2.0" +"oslo.i18n" = ">=3.15.3" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "oslo.config" +version = "8.7.0" +description = "Oslo Configuration API" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.2.0" +netaddr = ">=0.7.18" +"oslo.i18n" = ">=3.15.3" +PyYAML = ">=5.1" +requests = ">=2.18.0" +rfc3986 = ">=1.2.0" +stevedore = ">=1.20.0" + +[package.extras] +rst_generator = ["rst2txt (>=1.1.0)", "sphinx (>=1.8.0,!=2.1.0)"] +test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "hacking (>=3.0.1,<3.1.0)", "mypy (>=0.720)", "oslo.log (>=3.36.0)", "oslotest (>=3.2.0)", "pre-commit (>=2.6.0)", "requests-mock (>=1.5.0)", "stestr (>=2.1.0)", "testscenarios (>=0.4)", "testtools (>=2.2.0)"] + +[[package]] +name = "oslo.context" +version = "3.2.0" +description = "Oslo Context library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.2.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "oslo.db" +version = "9.0.0" +description = "Oslo Database library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +alembic = ">=0.9.6" +debtcollector = ">=1.2.0" +"oslo.config" = ">=5.2.0" +"oslo.i18n" = ">=3.15.3" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +SQLAlchemy = ">=1.2.0" +sqlalchemy-migrate = ">=0.11.0" +stevedore = ">=1.20.0" +testresources = ">=2.0.0" +testscenarios = ">=0.4" + +[package.extras] +mysql = ["PyMySQL (>=0.7.6)"] +postgresql = ["psycopg2 (>=2.8.0)"] +test = ["PyMySQL (>=0.7.6)", "bandit (>=1.6.0,<1.7.0)", "coverage (>=4.0,!=4.4)", "eventlet (>=0.18.2,!=0.18.3,!=0.20.1)", "fixtures (>=3.0.0)", "hacking (>=3.0.1,<3.1.0)", "oslo.context (>=2.19.2)", "oslotest (>=3.2.0)", "pifpaf (>=0.10.0)", "pre-commit (>=2.6.0)", "psycopg2 (>=2.8.0)", "python-subunit (>=1.0.0)", "stestr (>=2.0.0)", "testtools (>=2.2.0)"] + +[[package]] +name = "oslo.i18n" +version = "5.0.1" +description = "Oslo i18n library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +six = ">=1.10.0" + +[[package]] +name = "oslo.log" +version = "4.5.0" +description = "oslo.log library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.19.0" +"oslo.config" = ">=5.2.0" +"oslo.context" = ">=2.20.0" +"oslo.i18n" = ">=3.20.0" +"oslo.serialization" = ">=2.25.0" +"oslo.utils" = ">=3.36.0" +pbr = ">=3.1.1" +pyinotify = {version = ">=0.9.6", markers = "sys_platform != \"win32\" and sys_platform != \"darwin\" and sys_platform != \"sunos5\""} +python-dateutil = ">=2.7.0" + +[package.extras] +fixtures = ["fixtures (>=3.0.0)"] +systemd = ["systemd-python (>=234)"] +test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.5.1)", "fixtures (>=3.0.0)", "hacking (>=2.0.0,<2.1.0)", "oslotest (>=3.3.0)", "pre-commit (>=2.6.0)", "stestr (>=2.0.0)", "testtools (>=2.3.0)"] + +[[package]] +name = "oslo.messaging" +version = "12.8.0" +description = "Oslo Messaging API" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +amqp = ">=2.5.2" +cachetools = ">=2.0.0" +debtcollector = ">=1.2.0" +futurist = ">=1.2.0" +kombu = ">=4.6.6" +"oslo.config" = ">=5.2.0" +"oslo.log" = ">=3.36.0" +"oslo.middleware" = ">=3.31.0" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.service" = ">=1.24.0,<1.28.1 || >1.28.1" +"oslo.utils" = ">=3.37.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PyYAML = ">=3.13" +stevedore = ">=1.20.0" +WebOb = ">=1.7.1" + +[package.extras] +amqp1 = ["pyngus (>=2.2.0)"] +kafka = ["confluent-kafka (>=1.3.0)"] +test = ["bandit (>=1.6.0,<1.7.0)", "confluent-kafka (>=1.3.0)", "coverage (>=4.0,!=4.4)", "eventlet (>=0.23.0)", "fixtures (>=3.0.0)", "greenlet (>=0.4.15)", "hacking (>=3.0.1,<3.1.0)", "oslotest (>=3.2.0)", "pifpaf (>=2.2.0)", "pre-commit (>=2.6.0)", "pyngus (>=2.2.0)", "stestr (>=2.0.0)", "testscenarios (>=0.4)", "testtools (>=2.2.0)"] + +[[package]] +name = "oslo.middleware" +version = "4.2.0" +description = "Oslo Middleware library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.2.0" +Jinja2 = ">=2.10" +"oslo.config" = ">=5.2.0" +"oslo.context" = ">=2.19.2" +"oslo.i18n" = ">=3.15.3" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +statsd = ">=3.2.1" +stevedore = ">=1.20.0" +WebOb = ">=1.8.0" + +[[package]] +name = "oslo.policy" +version = "3.8.0" +description = "Oslo Policy library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +"oslo.config" = ">=6.0.0" +"oslo.context" = ">=2.22.0" +"oslo.i18n" = ">=3.15.3" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.40.0" +PyYAML = ">=5.1" +requests = ">=2.14.2" +stevedore = ">=1.20.0" + +[[package]] +name = "oslo.serialization" +version = "4.1.0" +description = "Oslo Serialization library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +msgpack = ">=0.5.2" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +pytz = ">=2013.6" + +[[package]] +name = "oslo.service" +version = "2.5.0" +description = "oslo.service library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.2.0" +eventlet = ">=0.25.2" +fixtures = ">=3.0.0" +greenlet = ">=0.4.15" +"oslo.concurrency" = ">=3.25.0" +"oslo.config" = ">=5.1.0" +"oslo.i18n" = ">=3.15.3" +"oslo.log" = ">=3.36.0" +"oslo.utils" = ">=3.40.2" +Paste = ">=2.0.2" +PasteDeploy = ">=1.5.0" +Routes = ">=2.3.1" +WebOb = ">=1.7.1" +Yappi = ">=1.0" + +[[package]] +name = "oslo.utils" +version = "4.9.0" +description = "Oslo Utility library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.2.0" +iso8601 = ">=0.1.11" +netaddr = ">=0.7.18" +netifaces = ">=0.10.4" +"oslo.i18n" = ">=3.15.3" +packaging = ">=20.4" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +pyparsing = ">=2.1.0" +pytz = ">=2013.6" + +[[package]] +name = "oslo.versionedobjects" +version = "2.4.0" +description = "Oslo Versioned Objects library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +iso8601 = ">=0.1.11" +netaddr = ">=0.7.18" +"oslo.concurrency" = ">=3.26.0" +"oslo.config" = ">=5.2.0" +"oslo.context" = ">=2.19.2" +"oslo.i18n" = ">=3.15.3" +"oslo.log" = ">=3.36.0" +"oslo.messaging" = ">=5.29.0" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=4.7.0" +WebOb = ">=1.7.1" + +[[package]] +name = "osprofiler" +version = "3.4.0" +description = "OpenStack Profiler Library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +netaddr = ">=0.7.18" +"oslo.concurrency" = ">=3.26.0" +"oslo.serialization" = ">=2.18.0" +"oslo.utils" = ">=3.33.0" +PrettyTable = ">=0.7.2,<0.8" +requests = ">=2.14.2" +six = ">=1.10.0" +WebOb = ">=1.7.1" + +[package.extras] +oslo_config = ["oslo.config (>=5.2.0)"] +test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.0)", "ddt (>=1.0.1)", "elasticsearch (>=2.0.0,<3.0.0)", "flake8-import-order (==0.18.1)", "hacking (>=3.1.0,<3.2.0)", "jaeger-client (>=3.8.0)", "openstackdocstheme (>=2.2.1)", "pymongo (>=3.0.2,!=3.1)", "redis (>=2.10.0)", "reno (>=3.1.0)", "sphinx (>=2.0.0,!=2.1.0)", "stestr (>=2.0.0)", "testtools (>=2.2.0)"] + +[[package]] +name = "ovs" +version = "2.13.3" +description = "Open vSwitch library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""} +sortedcontainers = "*" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "paste" +version = "3.5.0" +description = "Tools for using a Web Server Gateway Interface stack" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.4.0" + +[package.extras] +flup = ["flup"] +openid = ["python-openid"] + +[[package]] +name = "pastedeploy" +version = "2.1.1" +description = "Load, configure, and compose WSGI applications and servers" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +paste = ["paste"] +docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"] + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pbr" +version = "5.6.0" +description = "Python Build Reasonableness" +category = "main" +optional = false +python-versions = ">=2.6" + +[[package]] +name = "pecan" +version = "1.4.0" +description = "A WSGI object-dispatching web framework, designed to be lean and fast, with few dependencies." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +logutils = ">=0.3" +Mako = ">=0.4.0" +six = "*" +WebOb = ">=1.8" +WebTest = ">=1.3.1" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "prettytable" +version = "0.7.2" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyinotify" +version = "0.9.6" +description = "Linux filesystem events monitoring" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.4" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-editor" +version = "1.0.4" +description = "Programmatically open an editor, capture the result." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "python-mimeparse" +version = "1.6.0" +description = "A module provides basic functions for parsing mime-type names and matching them against a list of media-ranges." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pytz" +version = "2021.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pywin32" +version = "301" +description = "Python for Window Extensions" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "regex" +version = "2021.4.4" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "repoze.lru" +version = "0.7" +description = "A tiny LRU cache implementation and decorator" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +docs = ["sphinx"] +testing = ["coverage", "nose"] + +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "routes" +version = "2.5.1" +description = "Routing Recognition and Generation Tools" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +"repoze.lru" = ">=0.3" +six = "*" + +[package.extras] +docs = ["sphinx", "webob"] +middleware = ["webob"] + +[[package]] +name = "setproctitle" +version = "1.2.2" +description = "A Python module to customize the process title" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +test = ["pytest (>=6.1,<6.2)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "skyline-log" +version = "0.1.0" +description = "" +category = "main" +optional = false +python-versions = "^3.8" +develop = true + +[package.dependencies] +loguru = "*" + +[package.source] +type = "directory" +url = "../skyline-log" + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "soupsieve" +version = "2.2.1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "sqlalchemy" +version = "1.4.15" +description = "Database Abstraction Library" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\""} + +[package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +mariadb_connector = ["mariadb (>=1.0.1)"] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mypy = ["sqlalchemy2-stubs", "mypy (>=0.800)"] +mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"] +mysql_connector = ["mysqlconnector"] +oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] +postgresql_pg8000 = ["pg8000 (>=1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "sqlalchemy-migrate" +version = "0.13.0" +description = "Database schema migration for SQLAlchemy" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +decorator = "*" +pbr = ">=1.8" +six = ">=1.7.0" +SQLAlchemy = ">=0.9.6" +sqlparse = "*" +Tempita = ">=0.4" + +[[package]] +name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "statsd" +version = "3.3.0" +description = "A simple statsd client." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "stevedore" +version = "3.3.0" +description = "Manage dynamic plugins for Python applications" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "tempita" +version = "0.5.2" +description = "A very small text templating language" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "testresources" +version = "2.0.1" +description = "Testresources, a pyunit extension for managing expensive test resources" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pbr = ">=1.8" + +[package.extras] +test = ["docutils", "fixtures", "testtools"] + +[[package]] +name = "testscenarios" +version = "0.5.0" +description = "Testscenarios, a pyunit extension for dependency injection" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pbr = ">=0.11" +testtools = "*" + +[[package]] +name = "testtools" +version = "2.4.0" +description = "Extensions to the Python standard library unit testing framework" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +extras = ">=1.0.0" +fixtures = ">=1.3.0" +pbr = ">=0.11" +python-mimeparse = "*" +six = ">=1.4.0" +traceback2 = "*" +unittest2 = ">=1.0.0" + +[package.extras] +test = ["testresources", "testscenarios", "unittest2 (>=1.1.0)"] + +[[package]] +name = "tinyrpc" +version = "1.0.4" +description = "A small, modular, transport and protocol neutral RPC library that, among other things, supports JSON-RPC and zmq." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[package.extras] +gevent = ["gevent"] +httpclient = ["gevent-websocket", "requests", "websocket-client"] +jsonext = ["jsonext"] +msgpack = ["msgpack"] +websocket = ["gevent-websocket"] +wsgi = ["werkzeug"] +zmq = ["pyzmq"] + +[[package]] +name = "tokenize-rt" +version = "4.1.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "traceback2" +version = "1.4.0" +description = "Backports of the traceback module" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +linecache2 = "*" + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "unittest2" +version = "1.1.0" +description = "The new features in unittest backported to Python 2.4+." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +argparse = "*" +six = ">=1.4" +traceback2 = "*" + +[[package]] +name = "urllib3" +version = "1.26.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotlipy (>=0.6.0)"] + +[[package]] +name = "vine" +version = "5.0.0" +description = "Promises, promises, promises." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "waitress" +version = "2.0.0" +description = "Waitress WSGI server" +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] +testing = ["pytest", "pytest-cover", "coverage (>=5.0)"] + +[[package]] +name = "webob" +version = "1.8.7" +description = "WSGI request and response object" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" + +[package.extras] +docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"] +testing = ["pytest (>=3.1.0)", "coverage", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "webtest" +version = "2.0.35" +description = "Helper to test WSGI applications" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +beautifulsoup4 = "*" +six = "*" +waitress = ">=0.8.5" +WebOb = ">=1.2" + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.8)"] +tests = ["nose (<1.3.0)", "coverage", "mock", "pastedeploy", "wsgiproxy2", "pyquery"] + +[[package]] +name = "werkzeug" +version = "2.0.1" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "win32-setctime" +version = "1.0.3" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] + +[[package]] +name = "wrapt" +version = "1.12.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "yappi" +version = "1.3.2" +description = "Yet Another Python Profiler" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["gevent (>=20.6.2)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "3f95080a440a5cd28b1656dbc86d091f0ddf796e34f029cf2af69cc4520956df" + +[metadata.files] +add-trailing-comma = [ + {file = "add_trailing_comma-2.1.0-py2.py3-none-any.whl", hash = "sha256:f462403aa2e997e20855708edb57536d1d3310d5c5fac7e80542578eb47fdb10"}, + {file = "add_trailing_comma-2.1.0.tar.gz", hash = "sha256:f9864ffbc12ea4e54916a356d57341ab58f612867c2ad453339c51004807e8ce"}, +] +alembic = [ + {file = "alembic-1.6.2.tar.gz", hash = "sha256:fb9a39a7c68e55490be962fb5f70463d384d340e6563d6e3911447778e3b4576"}, +] +amqp = [ + {file = "amqp-5.0.6-py3-none-any.whl", hash = "sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"}, + {file = "amqp-5.0.6.tar.gz", hash = "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2"}, +] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +argparse = [ + {file = "argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"}, + {file = "argparse-1.4.0.tar.gz", hash = "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, + {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, + {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +cachetools = [ + {file = "cachetools-4.2.2-py3-none-any.whl", hash = "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001"}, + {file = "cachetools-4.2.2.tar.gz", hash = "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +click = [ + {file = "click-8.0.0-py3-none-any.whl", hash = "sha256:e90e62ced43dc8105fb9a26d62f0d9340b5c8db053a814e25d95c19873ae87db"}, + {file = "click-8.0.0.tar.gz", hash = "sha256:7d8c289ee437bcb0316820ccee14aefcb056e58d31830ecab8e47eda6540e136"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +debtcollector = [ + {file = "debtcollector-2.2.0-py3-none-any.whl", hash = "sha256:34663e5de257c67bf38827cfbea259c4d4ad27eba6b5a9d9242cb54076bfb4ad"}, + {file = "debtcollector-2.2.0.tar.gz", hash = "sha256:787981f4d235841bf6eb0467e23057fb1ac7ee24047c32028a8498b9128b6829"}, +] +decorator = [ + {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, + {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, +] +dnspython = [ + {file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"}, + {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, +] +eventlet = [ + {file = "eventlet-0.31.0-py2.py3-none-any.whl", hash = "sha256:27ae41fad9deed9bbf4166f3e3b65acc15d524d42210a518e5877da85a6b8c5d"}, + {file = "eventlet-0.31.0.tar.gz", hash = "sha256:b36ec2ecc003de87fc87b93197d77fea528aa0f9204a34fdf3b2f8d0f01e017b"}, +] +extras = [ + {file = "extras-1.0.0-py2.py3-none-any.whl", hash = "sha256:f689f08df47e2decf76aa6208c081306e7bd472630eb1ec8a875c67de2366e87"}, + {file = "extras-1.0.0.tar.gz", hash = "sha256:132e36de10b9c91d5d4cc620160a476e0468a88f16c9431817a6729611a81b4e"}, +] +fasteners = [ + {file = "fasteners-0.16-py2.py3-none-any.whl", hash = "sha256:74b6847e0a6bb3b56c8511af8e24c40e4cf7a774dfff5b251c260ed338096a4b"}, + {file = "fasteners-0.16.tar.gz", hash = "sha256:c995d8c26b017c5d6a6de9ad29a0f9cdd57de61ae1113d28fac26622b06a0933"}, +] +fixtures = [ + {file = "fixtures-3.0.0-py2.py3-none-any.whl", hash = "sha256:2a551b0421101de112d9497fb5f6fd25e5019391c0fbec9bad591ecae981420d"}, + {file = "fixtures-3.0.0.tar.gz", hash = "sha256:fcf0d60234f1544da717a9738325812de1f42c2fa085e2d9252d8fff5712b2ef"}, +] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] +futurist = [ + {file = "futurist-2.3.0-py3-none-any.whl", hash = "sha256:54f6ffc02f09316b28433e2b11b1ae5a2d25501fb07590acac0f3ee6e36ce45c"}, + {file = "futurist-2.3.0.tar.gz", hash = "sha256:174ea146adf303d7e5d7d6d34e3a01f4abf0382b03a6f9309bac2e2d54ffbed6"}, +] +greenlet = [ + {file = "greenlet-1.1.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c"}, + {file = "greenlet-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3"}, + {file = "greenlet-1.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922"}, + {file = "greenlet-1.1.0-cp27-cp27m-win32.whl", hash = "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821"}, + {file = "greenlet-1.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6"}, + {file = "greenlet-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f"}, + {file = "greenlet-1.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56"}, + {file = "greenlet-1.1.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16"}, + {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a"}, + {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831"}, + {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22"}, + {file = "greenlet-1.1.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5"}, + {file = "greenlet-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47"}, + {file = "greenlet-1.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08"}, + {file = "greenlet-1.1.0-cp35-cp35m-win32.whl", hash = "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131"}, + {file = "greenlet-1.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5"}, + {file = "greenlet-1.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e"}, + {file = "greenlet-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8"}, + {file = "greenlet-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb"}, + {file = "greenlet-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05"}, + {file = "greenlet-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f"}, + {file = "greenlet-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a"}, + {file = "greenlet-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da"}, + {file = "greenlet-1.1.0-cp38-cp38-win32.whl", hash = "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad"}, + {file = "greenlet-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8"}, + {file = "greenlet-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832"}, + {file = "greenlet-1.1.0-cp39-cp39-win32.whl", hash = "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11"}, + {file = "greenlet-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535"}, + {file = "greenlet-1.1.0.tar.gz", hash = "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +iso8601 = [ + {file = "iso8601-0.1.14-py2.py3-none-any.whl", hash = "sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004"}, + {file = "iso8601-0.1.14.tar.gz", hash = "sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79"}, +] +isort = [ + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, +] +jinja2 = [ + {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, + {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, +] +keystoneauth1 = [ + {file = "keystoneauth1-4.3.1-py3-none-any.whl", hash = "sha256:c4a80b79bc3e0412eb127fa761e80912614f8563646ca34b62bcd9d533f93077"}, + {file = "keystoneauth1-4.3.1.tar.gz", hash = "sha256:93605430a6d1424f31659bc5685e9dc1be9a6254e88c99f00cffc0a60c648a64"}, +] +kombu = [ + {file = "kombu-5.1.0-py3-none-any.whl", hash = "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"}, + {file = "kombu-5.1.0.tar.gz", hash = "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d"}, +] +linecache2 = [ + {file = "linecache2-1.0.0-py2.py3-none-any.whl", hash = "sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef"}, + {file = "linecache2-1.0.0.tar.gz", hash = "sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c"}, +] +loguru = [ + {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, + {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, +] +logutils = [ + {file = "logutils-0.3.5.tar.gz", hash = "sha256:bc058a25d5c209461f134e1f03cab637d66a7a5ccc12e593db56fbb279899a82"}, +] +mako = [ + {file = "Mako-1.1.4-py2.py3-none-any.whl", hash = "sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e"}, + {file = "Mako-1.1.4.tar.gz", hash = "sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab"}, +] +markupsafe = [ + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +msgpack = [ + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1026dcc10537d27dd2d26c327e552f05ce148977e9d7b9f1718748281b38c841"}, + {file = "msgpack-1.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fe07bc6735d08e492a327f496b7850e98cb4d112c56df69b0c844dbebcbb47f6"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9ea52fff0473f9f3000987f313310208c879493491ef3ccf66268eff8d5a0326"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26a1759f1a88df5f1d0b393eb582ec022326994e311ba9c5818adc5374736439"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:497d2c12426adcd27ab83144057a705efb6acc7e85957a51d43cdcf7f258900f"}, + {file = "msgpack-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:e89ec55871ed5473a041c0495b7b4e6099f6263438e0bd04ccd8418f92d5d7f2"}, + {file = "msgpack-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4355d2193106c7aa77c98fc955252a737d8550320ecdb2e9ac701e15e2943bc"}, + {file = "msgpack-1.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:d6c64601af8f3893d17ec233237030e3110f11b8a962cb66720bf70c0141aa54"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f484cd2dca68502de3704f056fa9b318c94b1539ed17a4c784266df5d6978c87"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f3e6aaf217ac1c7ce1563cf52a2f4f5d5b1f64e8729d794165db71da57257f0c"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8521e5be9e3b93d4d5e07cb80b7e32353264d143c1f072309e1863174c6aadb1"}, + {file = "msgpack-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:31c17bbf2ae5e29e48d794c693b7ca7a0c73bd4280976d408c53df421e838d2a"}, + {file = "msgpack-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8ffb24a3b7518e843cd83538cf859e026d24ec41ac5721c18ed0c55101f9775b"}, + {file = "msgpack-1.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b28c0876cce1466d7c2195d7658cf50e4730667196e2f1355c4209444717ee06"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87869ba567fe371c4555d2e11e4948778ab6b59d6cc9d8460d543e4cfbbddd1c"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b55f7db883530b74c857e50e149126b91bb75d35c08b28db12dcb0346f15e46e"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ac25f3e0513f6673e8b405c3a80500eb7be1cf8f57584be524c4fa78fe8e0c83"}, + {file = "msgpack-1.0.2-cp38-cp38-win32.whl", hash = "sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9"}, + {file = "msgpack-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e36a812ef4705a291cdb4a2fd352f013134f26c6ff63477f20235138d1d21009"}, + {file = "msgpack-1.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2a5866bdc88d77f6e1370f82f2371c9bc6fc92fe898fa2dec0c5d4f5435a2694"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:92be4b12de4806d3c36810b0fe2aeedd8d493db39e2eb90742b9c09299eb5759"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6bd7990a2c2dabe926b7e62a92886ccbf809425c347ae7de277067f97c2887"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5a9ee2540c78659a1dd0b110f73773533ee3108d4e1219b5a15a8d635b7aca0e"}, + {file = "msgpack-1.0.2-cp39-cp39-win32.whl", hash = "sha256:c747c0cc08bd6d72a586310bda6ea72eeb28e7505990f342552315b229a19b33"}, + {file = "msgpack-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:d8167b84af26654c1124857d71650404336f4eb5cc06900667a493fc619ddd9f"}, + {file = "msgpack-1.0.2.tar.gz", hash = "sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984"}, +] +mypy = [ + {file = "mypy-0.812-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49"}, + {file = "mypy-0.812-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c"}, + {file = "mypy-0.812-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521"}, + {file = "mypy-0.812-cp35-cp35m-win_amd64.whl", hash = "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"}, + {file = "mypy-0.812-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a"}, + {file = "mypy-0.812-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c"}, + {file = "mypy-0.812-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6"}, + {file = "mypy-0.812-cp36-cp36m-win_amd64.whl", hash = "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064"}, + {file = "mypy-0.812-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56"}, + {file = "mypy-0.812-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8"}, + {file = "mypy-0.812-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7"}, + {file = "mypy-0.812-cp37-cp37m-win_amd64.whl", hash = "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564"}, + {file = "mypy-0.812-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506"}, + {file = "mypy-0.812-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5"}, + {file = "mypy-0.812-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66"}, + {file = "mypy-0.812-cp38-cp38-win_amd64.whl", hash = "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e"}, + {file = "mypy-0.812-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a"}, + {file = "mypy-0.812-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a"}, + {file = "mypy-0.812-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97"}, + {file = "mypy-0.812-cp39-cp39-win_amd64.whl", hash = "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df"}, + {file = "mypy-0.812-py3-none-any.whl", hash = "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4"}, + {file = "mypy-0.812.tar.gz", hash = "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +netaddr = [ + {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, + {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, +] +netifaces = [ + {file = "netifaces-0.10.9-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29"}, + {file = "netifaces-0.10.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b"}, + {file = "netifaces-0.10.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc"}, + {file = "netifaces-0.10.9-cp27-cp27m-win32.whl", hash = "sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2"}, + {file = "netifaces-0.10.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b"}, + {file = "netifaces-0.10.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084"}, + {file = "netifaces-0.10.9-cp33-cp33m-manylinux1_i686.whl", hash = "sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994"}, + {file = "netifaces-0.10.9-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940"}, + {file = "netifaces-0.10.9-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7"}, + {file = "netifaces-0.10.9-cp34-cp34m-win32.whl", hash = "sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24"}, + {file = "netifaces-0.10.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea"}, + {file = "netifaces-0.10.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b"}, + {file = "netifaces-0.10.9-cp35-cp35m-win32.whl", hash = "sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae"}, + {file = "netifaces-0.10.9-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc"}, + {file = "netifaces-0.10.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89"}, + {file = "netifaces-0.10.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c"}, + {file = "netifaces-0.10.9-cp36-cp36m-win32.whl", hash = "sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe"}, + {file = "netifaces-0.10.9-cp36-cp36m-win_amd64.whl", hash = "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215"}, + {file = "netifaces-0.10.9-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa"}, + {file = "netifaces-0.10.9-cp37-cp37m-win32.whl", hash = "sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1"}, + {file = "netifaces-0.10.9-cp37-cp37m-win_amd64.whl", hash = "sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42"}, + {file = "netifaces-0.10.9.tar.gz", hash = "sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3"}, +] +neutron-lib = [ + {file = "neutron-lib-2.11.0.tar.gz", hash = "sha256:af47a5c1f84f05a281038625064b7f0e6c6856d56c3ec3fb06184789acef4685"}, + {file = "neutron_lib-2.11.0-py3-none-any.whl", hash = "sha256:15427f8e2b5b119d8a9dafa18fe247b2fd18c56e838f246da7b01be4dc9a777c"}, +] +os-ken = [ + {file = "os-ken-1.4.0.tar.gz", hash = "sha256:256614dbe468f1f5bf16dfb290598c856e9a40f61269556d90e43ca8b5b0a1c8"}, + {file = "os_ken-1.4.0-py3-none-any.whl", hash = "sha256:b65d8d2e6c57dacb59828766febe6772f6f166109ce1eb5bcbcf98afcfff3a54"}, +] +os-service-types = [ + {file = "os-service-types-1.7.0.tar.gz", hash = "sha256:31800299a82239363995b91f1ebf9106ac7758542a1e4ef6dc737a5932878c6c"}, + {file = "os_service_types-1.7.0-py2.py3-none-any.whl", hash = "sha256:0505c72205690910077fb72b88f2a1f07533c8d39f2fe75b29583481764965d6"}, +] +os-traits = [ + {file = "os-traits-2.5.0.tar.gz", hash = "sha256:6168dcda9e6f971432a46aca8e88712dcdb85aecaacc95a8bfe8777cd358da0b"}, + {file = "os_traits-2.5.0-py3-none-any.whl", hash = "sha256:f2697a31e61d5442c44fdb6c60688e0fefdf3edb83afb4a14764fae0cd9c4eb3"}, +] +"oslo.concurrency" = [ + {file = "oslo.concurrency-4.4.0-py3-none-any.whl", hash = "sha256:a4e0186c246d9724f22e1125c13cae5d7838c81129e75b5ee40347e431958225"}, + {file = "oslo.concurrency-4.4.0.tar.gz", hash = "sha256:0d1d0a341ead03f4e5638c368de99baacd943011c5cceece43106885470edf69"}, +] +"oslo.config" = [ + {file = "oslo.config-8.7.0-py3-none-any.whl", hash = "sha256:5b1b9439bfb76e0091172aee77b509144f22e08127ddee4d10a7eb5407740b07"}, + {file = "oslo.config-8.7.0.tar.gz", hash = "sha256:5b28db6e8716feddb83fc8d97584513d7f48b54ee774c74b0c5a8d61c8d4fa73"}, +] +"oslo.context" = [ + {file = "oslo.context-3.2.0-py3-none-any.whl", hash = "sha256:ed71f2431bfc71b2cdb220ae2206a3dd22c621b5cbc9241bb69998a47a591d8c"}, + {file = "oslo.context-3.2.0.tar.gz", hash = "sha256:0e7d96a95c276de2da0f458ef34153347327ac34fe33b264be2bb59eac51e620"}, +] +"oslo.db" = [ + {file = "oslo.db-9.0.0-py3-none-any.whl", hash = "sha256:af38fb7684e300017d45be94ab9f7ba828aede11c3c697874d6a0ca98ccf3285"}, + {file = "oslo.db-9.0.0.tar.gz", hash = "sha256:d1b7b699868c889e0a16740c507cb8219b6f0baea77e11e06071e09e976ef397"}, +] +"oslo.i18n" = [ + {file = "oslo.i18n-5.0.1-py3-none-any.whl", hash = "sha256:99a6453b9b7a9d1603ba6c32e6ab8c738af95f6573215682a33c8028340bdccd"}, + {file = "oslo.i18n-5.0.1.tar.gz", hash = "sha256:3484b71e30f75c437523302d1151c291caf4098928269ceec65ce535456e035b"}, +] +"oslo.log" = [ + {file = "oslo.log-4.5.0-py3-none-any.whl", hash = "sha256:8539cfa12f49c52d3ccfaf29c72935c80d784bce90a8733684db2b830be6a33c"}, + {file = "oslo.log-4.5.0.tar.gz", hash = "sha256:63619fc00483f37c7ae6a6d6775d1ebb2b57e241c731f0295d1473b2c01c7278"}, +] +"oslo.messaging" = [ + {file = "oslo.messaging-12.8.0-py3-none-any.whl", hash = "sha256:d8f72892e03876402c83e81f00654dade3e0772c22bcf5cdb681eb9e2d9ed7e1"}, + {file = "oslo.messaging-12.8.0.tar.gz", hash = "sha256:175bdf808722f7679508f95a23b59e78a7a71601a784aee53c12f24b0f304ecb"}, +] +"oslo.middleware" = [ + {file = "oslo.middleware-4.2.0-py3-none-any.whl", hash = "sha256:54be9f1d03f63b0e2b45c17b804dea5e00f564826c5c4e0573e3494046660291"}, + {file = "oslo.middleware-4.2.0.tar.gz", hash = "sha256:f6bb2b2896737bf37c8f0ff034fd74245b7a1d2d5cb1252b672b44fbe3249d88"}, +] +"oslo.policy" = [ + {file = "oslo.policy-3.8.0-py3-none-any.whl", hash = "sha256:15a6e3e6677ada933d48b05b29e0c73a4d63b7267f0ec183c05ba7d7e01310c6"}, + {file = "oslo.policy-3.8.0.tar.gz", hash = "sha256:31f4a4c94fddc1247e829349575b4358e21412ee0a4d4d9c1b37ba6be8c3f022"}, +] +"oslo.serialization" = [ + {file = "oslo.serialization-4.1.0-py3-none-any.whl", hash = "sha256:a0acf0ff7ca88b3ee6514713571f614b5c20870005ed0eb90408fa7f9f3edb60"}, + {file = "oslo.serialization-4.1.0.tar.gz", hash = "sha256:cecc7794df806c85cb70dbd6c2b3af19bc68047ad29e3c6442be90a0a4de5379"}, +] +"oslo.service" = [ + {file = "oslo.service-2.5.0-py3-none-any.whl", hash = "sha256:a17d19890e4df00965d5d9ff1b7eb16efb2f0bc087f252e309b0176c03af8033"}, + {file = "oslo.service-2.5.0.tar.gz", hash = "sha256:147da1140bc112f0aba810ebf05f16775a181a7d9e8e87e19e8a59a49ecbcc28"}, +] +"oslo.utils" = [ + {file = "oslo.utils-4.9.0-py3-none-any.whl", hash = "sha256:c10740e4462c32956afa1ba5156191cb678b2b63c60485d1a73542d1622677d6"}, + {file = "oslo.utils-4.9.0.tar.gz", hash = "sha256:4d29cae87f0ce48ec0e1c5577787c5cd10723c614f2d5a642f42c5efdf787e9c"}, +] +"oslo.versionedobjects" = [ + {file = "oslo.versionedobjects-2.4.0-py3-none-any.whl", hash = "sha256:a3598db096a09c1d0a50998c3496bce3c8a963355611874d8ae4eba9bc7a5379"}, + {file = "oslo.versionedobjects-2.4.0.tar.gz", hash = "sha256:f0149e557d962365f53e61b7ce0aa4d7037d2d83a0db4fecba1e964fd4949926"}, +] +osprofiler = [ + {file = "osprofiler-3.4.0-py3-none-any.whl", hash = "sha256:4ccde8ab98fd2861008033ef7fae3f4d1bc63f78a5f203ffd59107dea8a35cf9"}, + {file = "osprofiler-3.4.0.tar.gz", hash = "sha256:f7fe387f4dd681bbf92b9fbe857cc90e89cfe96d44522a2727540902310a9ed6"}, +] +ovs = [ + {file = "ovs-2.13.3.tar.gz", hash = "sha256:8b46cec865a36bfa7863df29f4f761589c1e075df7d4aa2faedc52fe1e3d80b6"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +paste = [ + {file = "Paste-3.5.0-py2.py3-none-any.whl", hash = "sha256:8e08200a570f7e29dfafd4eea0e1b38a6193cfda6446bb515db74250b632c53b"}, + {file = "Paste-3.5.0.tar.gz", hash = "sha256:1b095c42dc91d426f3ae85101796b14d265887f8f36f3aad143a5f29effdc39d"}, +] +pastedeploy = [ + {file = "PasteDeploy-2.1.1-py2.py3-none-any.whl", hash = "sha256:14923cfd6ad4281b570693afc278bab5076fbdd4cd15aa9d99b042d694aa4217"}, + {file = "PasteDeploy-2.1.1.tar.gz", hash = "sha256:6dead6ab9823a85d585ef27f878bc647f787edb9ca8da0716aa9f1261b464817"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pbr = [ + {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"}, + {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"}, +] +pecan = [ + {file = "pecan-1.4.0.tar.gz", hash = "sha256:4b2acd6802a04b59e306d0a6ccf37701d24376f4dc044bbbafba3afdf9d3389a"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +prettytable = [ + {file = "prettytable-0.7.2.tar.bz2", hash = "sha256:853c116513625c738dc3ce1aee148b5b5757a86727e67eff6502c7ca59d43c36"}, + {file = "prettytable-0.7.2.tar.gz", hash = "sha256:2d5460dc9db74a32bcc8f9f67de68b2c4f4d2f01fa3bd518764c69156d9cacd9"}, + {file = "prettytable-0.7.2.zip", hash = "sha256:a53da3b43d7a5c229b5e3ca2892ef982c46b7923b51e98f0db49956531211c4f"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] +pyinotify = [ + {file = "pyinotify-0.9.6.tar.gz", hash = "sha256:9c998a5d7606ca835065cdabc013ae6c66eb9ea76a00a1e3bc6e0cfe2b4f71f4"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +python-editor = [ + {file = "python-editor-1.0.4.tar.gz", hash = "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b"}, + {file = "python_editor-1.0.4-py2-none-any.whl", hash = "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"}, + {file = "python_editor-1.0.4-py2.7.egg", hash = "sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"}, + {file = "python_editor-1.0.4-py3-none-any.whl", hash = "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d"}, + {file = "python_editor-1.0.4-py3.5.egg", hash = "sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77"}, +] +python-mimeparse = [ + {file = "python-mimeparse-1.6.0.tar.gz", hash = "sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78"}, + {file = "python_mimeparse-1.6.0-py2.py3-none-any.whl", hash = "sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282"}, +] +pytz = [ + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, +] +pywin32 = [ + {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, + {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, + {file = "pywin32-301-cp36-cp36m-win32.whl", hash = "sha256:c866f04a182a8cb9b7855de065113bbd2e40524f570db73ef1ee99ff0a5cc2f0"}, + {file = "pywin32-301-cp36-cp36m-win_amd64.whl", hash = "sha256:dafa18e95bf2a92f298fe9c582b0e205aca45c55f989937c52c454ce65b93c78"}, + {file = "pywin32-301-cp37-cp37m-win32.whl", hash = "sha256:98f62a3f60aa64894a290fb7494bfa0bfa0a199e9e052e1ac293b2ad3cd2818b"}, + {file = "pywin32-301-cp37-cp37m-win_amd64.whl", hash = "sha256:fb3b4933e0382ba49305cc6cd3fb18525df7fd96aa434de19ce0878133bf8e4a"}, + {file = "pywin32-301-cp38-cp38-win32.whl", hash = "sha256:88981dd3cfb07432625b180f49bf4e179fb8cbb5704cd512e38dd63636af7a17"}, + {file = "pywin32-301-cp38-cp38-win_amd64.whl", hash = "sha256:8c9d33968aa7fcddf44e47750e18f3d034c3e443a707688a008a2e52bbef7e96"}, + {file = "pywin32-301-cp39-cp39-win32.whl", hash = "sha256:595d397df65f1b2e0beaca63a883ae6d8b6df1cdea85c16ae85f6d2e648133fe"}, + {file = "pywin32-301-cp39-cp39-win_amd64.whl", hash = "sha256:87604a4087434cd814ad8973bd47d6524bd1fa9e971ce428e76b62a5e0860fdf"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +regex = [ + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, +] +"repoze.lru" = [ + {file = "repoze.lru-0.7-py3-none-any.whl", hash = "sha256:f77bf0e1096ea445beadd35f3479c5cff2aa1efe604a133e67150bc8630a62ea"}, + {file = "repoze.lru-0.7.tar.gz", hash = "sha256:0429a75e19380e4ed50c0694e26ac8819b4ea7851ee1fc7583c8572db80aff77"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] +routes = [ + {file = "Routes-2.5.1-py2.py3-none-any.whl", hash = "sha256:fab5a042a3a87778eb271d053ca2723cadf43c95b471532a191a48539cb606ea"}, + {file = "Routes-2.5.1.tar.gz", hash = "sha256:b6346459a15f0cbab01a45a90c3d25caf980d4733d628b4cc1952b865125d053"}, +] +setproctitle = [ + {file = "setproctitle-1.2.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9106bcbacae534b6f82955b176723f1b2ca6514518aab44dffec05a583f8dca8"}, + {file = "setproctitle-1.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:30bc7a769a4451639a0adcbc97bdf7a6e9ac0ef3ddad8d63eb1e338edb3ebeda"}, + {file = "setproctitle-1.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e8ef655eab26e83ec105ce79036bb87e5f2bf8ba2d6f48afdd9595ef7647fcf4"}, + {file = "setproctitle-1.2.2-cp36-cp36m-win32.whl", hash = "sha256:0df728d0d350e6b1ad8436cc7add052faebca6f4d03257182d427d86d4422065"}, + {file = "setproctitle-1.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:5260e8700c5793d48e79c5e607e8e552e795b698491a4b9bb9111eb74366a450"}, + {file = "setproctitle-1.2.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba1fb32e7267330bd9f72e69e076777a877f1cb9be5beac5e62d1279e305f37f"}, + {file = "setproctitle-1.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e696c93d93c23f377ccd2d72e38908d3dbfc90e45561602b805f53f2627d42ea"}, + {file = "setproctitle-1.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:fbf914179dc4540ee6bfd8228b4cc1f1f6fb12dad66b72b5c9b955b222403220"}, + {file = "setproctitle-1.2.2-cp37-cp37m-win32.whl", hash = "sha256:28b884e1cb9a53974e15838864283f9bad774b5c7db98c9609416bd123cb9fd1"}, + {file = "setproctitle-1.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a11d329f33221443317e2aeaee9442f22fcae25be3aa4fb8489e4f7b1f65cdd2"}, + {file = "setproctitle-1.2.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e13a5c1d9c369cb11cdfc4b75be432b83eb3205c95a69006008ffd4366f87b9e"}, + {file = "setproctitle-1.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c611f65bc9de5391a1514de556f71101e6531bb0715d240efd3e9732626d5c9e"}, + {file = "setproctitle-1.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:bc4393576ed3ac87ddac7d1bd0faaa2fab24840a025cc5f3c21d14cf0c9c8a12"}, + {file = "setproctitle-1.2.2-cp38-cp38-win32.whl", hash = "sha256:17598f38be9ef499d74f2380bf76b558be72e87da75d66b153350e586649171b"}, + {file = "setproctitle-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:0d160d46c8f3567e0aa27b26b1f36e03122e3de475aacacc14a92b8fe45b648a"}, + {file = "setproctitle-1.2.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:077943272d0490b3f43d17379432d5e49c263f608fdf4cf624b419db762ca72b"}, + {file = "setproctitle-1.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:970798d948f0c90a3eb0f8750f08cb215b89dcbee1b55ffb353ad62d9361daeb"}, + {file = "setproctitle-1.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3f6136966c81daaf5b4b010613fe33240a045a4036132ef040b623e35772d998"}, + {file = "setproctitle-1.2.2-cp39-cp39-win32.whl", hash = "sha256:249526a06f16d493a2cb632abc1b1fdfaaa05776339a50dd9f27c941f6ff1383"}, + {file = "setproctitle-1.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:4fc5bebd34f451dc87d2772ae6093adea1ea1dc29afc24641b250140decd23bb"}, + {file = "setproctitle-1.2.2.tar.gz", hash = "sha256:7dfb472c8852403d34007e01d6e3c68c57eb66433fb8a5c77b13b89a160d97df"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +skyline-log = [] +sortedcontainers = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] +soupsieve = [ + {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"}, + {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.15-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:22141a05d0f60df57ae334b589dbd081213c257a80d448ff499a3b6efd1998d3"}, + {file = "SQLAlchemy-1.4.15-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c12b7dc8e37442eef74afc7f4f99eb4ec6d796215fc4499ca32c7ca48f353cb3"}, + {file = "SQLAlchemy-1.4.15-cp27-cp27m-win32.whl", hash = "sha256:5ec8d34c8a9f467178b581a48ccef9163cb553015925e4665d7af495c3c958d9"}, + {file = "SQLAlchemy-1.4.15-cp27-cp27m-win_amd64.whl", hash = "sha256:324fb6e1f41afd5bdf0a34cfd011999213dcd543b83efa9dcc868f9e64a9ff7f"}, + {file = "SQLAlchemy-1.4.15-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8a4d26fa3f00344f9b34402f8a52b58941ba0d4b0ca80d5b05be39ec35b2eb8e"}, + {file = "SQLAlchemy-1.4.15-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a2c2965698807e53f1f4da1cc9d68f1c1dda9139ef5a96d18921be4e253d687e"}, + {file = "SQLAlchemy-1.4.15-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10068984bf334dd0b03ea83550b45667be968789bd0033215d30053649b0dd1b"}, + {file = "SQLAlchemy-1.4.15-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:beb1a6560d65c46d52c6ac402a806b8d24a6f2ee3f96fbbd4cfa371db24c3b3a"}, + {file = "SQLAlchemy-1.4.15-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6521e3b2f58a9ec2ad84b24efa88e61b8d355a6e481b459dcb64cadd14ba74d7"}, + {file = "SQLAlchemy-1.4.15-cp36-cp36m-win32.whl", hash = "sha256:6072231bdf976722ce92a8d1335e5b2d7ed0d7ee28667c00537b58cf7d68c41d"}, + {file = "SQLAlchemy-1.4.15-cp36-cp36m-win_amd64.whl", hash = "sha256:4d3cc347db370cc0d14dd724a9f280f4b4a0447ad77a228dd20792c4736f0b0e"}, + {file = "SQLAlchemy-1.4.15-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:3845b3af8a412230cc91fd32103a74d558566fea96c1b8775abb7ec65c3ef5de"}, + {file = "SQLAlchemy-1.4.15-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8410319b084b708c4ee0bc0d82f4b01623883595b5d8333ec704788940cc7293"}, + {file = "SQLAlchemy-1.4.15-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c1151b26f8bc53a69dc82f782560568186625d7b70bece4914ca459be1f539e1"}, + {file = "SQLAlchemy-1.4.15-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec88907048fbade9712de08e648203d95221cad5a3b8a459cc3724c1bffb9281"}, + {file = "SQLAlchemy-1.4.15-cp37-cp37m-win32.whl", hash = "sha256:e3e627e0f57b6f101ecabe39b90261625deedc91ec659cd4226f522bd3dd0020"}, + {file = "SQLAlchemy-1.4.15-cp37-cp37m-win_amd64.whl", hash = "sha256:70036b7fc86b8dc0c04e186107ee6371e8f9a8fb35980d483cc4d114b298b19f"}, + {file = "SQLAlchemy-1.4.15-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:21e0d18dab96515670e96e53a7e7207ba5cee6cd56b312447f2772d61d37d9b8"}, + {file = "SQLAlchemy-1.4.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260a79673c1234a20d7a16ee3ac6711c3f1b81363ebb208921d512fdb9f6a12e"}, + {file = "SQLAlchemy-1.4.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c2ff45be0eacf4ac290fe546064df257e8be899e3b191a39df3e41a2d9a0797"}, + {file = "SQLAlchemy-1.4.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:403e94a1862c6217e7bd71950191d58ad313ea976e7d128c9afb6b9934d2d6a2"}, + {file = "SQLAlchemy-1.4.15-cp38-cp38-win32.whl", hash = "sha256:05ea2c275603b3fb5ce761d0ccabe47a376ed8a48f70e1d4c80a71f185224d3f"}, + {file = "SQLAlchemy-1.4.15-cp38-cp38-win_amd64.whl", hash = "sha256:75becbc5ac452dac28d8d5aeb0406ddd3a1d808726a5fd0d5b696fad0b71d951"}, + {file = "SQLAlchemy-1.4.15-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:92dfb2ac7b44873901f87f3e0bb5c63469b76c5c3cabbf8124332e0dd1172410"}, + {file = "SQLAlchemy-1.4.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a31062468a184eb046eb09eadf296e3652d916793e32829082b3eda3367be5e8"}, + {file = "SQLAlchemy-1.4.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6248934b6e1841a794d5d12e2d43e32c2a7c64a36a059c612d4d66b312b3604f"}, + {file = "SQLAlchemy-1.4.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3fab43abe335a44aed3fbf98be619f021cbee2160718ecedc5fe4fa41296f7e"}, + {file = "SQLAlchemy-1.4.15-cp39-cp39-win32.whl", hash = "sha256:5642d64feeab65ae662c8e46eccc3db4a3100c9572dcfa29063751e2d1940e78"}, + {file = "SQLAlchemy-1.4.15-cp39-cp39-win_amd64.whl", hash = "sha256:17ce3009c69ac361d871bed3c9c30cf405d2739934d83322272bd455a697c874"}, + {file = "SQLAlchemy-1.4.15.tar.gz", hash = "sha256:0ff100c75cd175f35f4d24375a0b3d82461f5b1af5fc8d112ef0e5ceea8049e6"}, +] +sqlalchemy-migrate = [ + {file = "sqlalchemy-migrate-0.13.0.tar.gz", hash = "sha256:0bc02e292a040ade5e35a01d3ea744119e1309cdddb704fdb99bac13236614f8"}, + {file = "sqlalchemy_migrate-0.13.0-py2.py3-none-any.whl", hash = "sha256:e5d2348db19a5062132d93e3b4d9e7644af552fffbec4c78cc5358f848d2f6c1"}, +] +sqlparse = [ + {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, + {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, +] +statsd = [ + {file = "statsd-3.3.0-py2.py3-none-any.whl", hash = "sha256:c610fb80347fca0ef62666d241bce64184bd7cc1efe582f9690e045c25535eaa"}, + {file = "statsd-3.3.0.tar.gz", hash = "sha256:e3e6db4c246f7c59003e51c9720a51a7f39a396541cb9b147ff4b14d15b5dd1f"}, +] +stevedore = [ + {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, + {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, +] +tempita = [ + {file = "Tempita-0.5.2.tar.gz", hash = "sha256:cacecf0baa674d356641f1d406b8bff1d756d739c46b869a54de515d08e6fc9c"}, +] +testresources = [ + {file = "testresources-2.0.1-py2.py3-none-any.whl", hash = "sha256:67a361c3a2412231963b91ab04192209aa91a1aa052f0ab87245dbea889d1282"}, + {file = "testresources-2.0.1.tar.gz", hash = "sha256:ee9d1982154a1e212d4e4bac6b610800bfb558e4fb853572a827bc14a96e4417"}, +] +testscenarios = [ + {file = "testscenarios-0.5.0-py2.py3-none-any.whl", hash = "sha256:480263fa5d6e618125bdf092aab129a3aeed5996b1e668428f12cc56d6d01d28"}, + {file = "testscenarios-0.5.0.tar.gz", hash = "sha256:c257cb6b90ea7e6f8fef3158121d430543412c9a87df30b5dde6ec8b9b57a2b6"}, +] +testtools = [ + {file = "testtools-2.4.0-py2.py3-none-any.whl", hash = "sha256:36ff4998177c7d32ffe5fed3d541cb9ee62618a3b8e745c55510698997774ba4"}, + {file = "testtools-2.4.0.tar.gz", hash = "sha256:64c974a6cca4385d05f4bbfa2deca1c39ce88ede31c3448bee86a7259a9a61c8"}, +] +tinyrpc = [ + {file = "tinyrpc-1.0.4.tar.gz", hash = "sha256:4b41fab967fb1c978f573bf0d609a3b12cc3b6ed62bd3108f43f575563746396"}, +] +tokenize-rt = [ + {file = "tokenize_rt-4.1.0-py2.py3-none-any.whl", hash = "sha256:b37251fa28c21e8cce2e42f7769a35fba2dd2ecafb297208f9a9a8add3ca7793"}, + {file = "tokenize_rt-4.1.0.tar.gz", hash = "sha256:ab339b5ff829eb5e198590477f9c03c84e762b3e455e74c018956e7e326cbc70"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +traceback2 = [ + {file = "traceback2-1.4.0-py2.py3-none-any.whl", hash = "sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23"}, + {file = "traceback2-1.4.0.tar.gz", hash = "sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +unittest2 = [ + {file = "unittest2-1.1.0-py2.py3-none-any.whl", hash = "sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8"}, + {file = "unittest2-1.1.0.tar.gz", hash = "sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579"}, +] +urllib3 = [ + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, +] +vine = [ + {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, + {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, +] +waitress = [ + {file = "waitress-2.0.0-py3-none-any.whl", hash = "sha256:29af5a53e9fb4e158f525367678b50053808ca6c21ba585754c77d790008c746"}, + {file = "waitress-2.0.0.tar.gz", hash = "sha256:69e1f242c7f80273490d3403c3976f3ac3b26e289856936d1f620ed48f321897"}, +] +webob = [ + {file = "WebOb-1.8.7-py2.py3-none-any.whl", hash = "sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b"}, + {file = "WebOb-1.8.7.tar.gz", hash = "sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"}, +] +webtest = [ + {file = "WebTest-2.0.35-py2.py3-none-any.whl", hash = "sha256:44ddfe99b5eca4cf07675e7222c81dd624d22f9a26035d2b93dc8862dc1153c6"}, + {file = "WebTest-2.0.35.tar.gz", hash = "sha256:aac168b5b2b4f200af4e35867cf316712210e3d5db81c1cbdff38722647bb087"}, +] +werkzeug = [ + {file = "Werkzeug-2.0.1-py3-none-any.whl", hash = "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"}, + {file = "Werkzeug-2.0.1.tar.gz", hash = "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42"}, +] +win32-setctime = [ + {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, + {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, +] +wrapt = [ + {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, +] +yappi = [ + {file = "yappi-1.3.2.tar.gz", hash = "sha256:a51d3e6e5563cc74b5bb82ed6e7bd44a9c1a7eae3d97e4d52e9465edb3a8da8d"}, +] diff --git a/libs/skyline-policy-manager/poetry.toml b/libs/skyline-policy-manager/poetry.toml new file mode 100644 index 0000000..ab1033b --- /dev/null +++ b/libs/skyline-policy-manager/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/libs/skyline-policy-manager/pyproject.toml b/libs/skyline-policy-manager/pyproject.toml new file mode 100644 index 0000000..a6e8e2a --- /dev/null +++ b/libs/skyline-policy-manager/pyproject.toml @@ -0,0 +1,64 @@ +[tool.poetry] +name = "skyline-policy-manager" +version = "0.1.0" +description = "" +license = "Apache-2.0" +authors = ["OpenStack "] + +[tool.poetry.dependencies] +python = "^3.8" +pydantic = "*" +"oslo.policy" = "*" +Werkzeug = "*" +click = "*" +skyline-log = "*" + +[tool.poetry.dev-dependencies] +pytest = "*" +mypy = "*" +black = "^20.8b1" +isort = "*" +flake8 = "*" +add-trailing-comma = "*" +skyline-log = {path = "../skyline-log", develop = true} +"oslo.log" = "*" +neutron-lib = "*" + +[tool.poetry.scripts] +skyline-policy-manager = "skyline_policy_manager.cmd.manage:main" + +[tool.black] +line-length = 98 +target-version = ['py38'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + )/ + | exclude.py +) +''' +verbos = true + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +line_length = 98 +reverse_relative = true +combine_as_imports = true + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/__init__.py b/libs/skyline-policy-manager/src/skyline_policy_manager/__init__.py new file mode 100644 index 0000000..0b747ca --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "0.1.0" diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/__main__.py b/libs/skyline-policy-manager/src/skyline_policy_manager/__main__.py new file mode 100644 index 0000000..94e6df8 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/__main__.py @@ -0,0 +1,44 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from pprint import pp + +from werkzeug.serving import run_simple +from werkzeug.wrappers import Request, Response + + +@Request.application +def application(request): + body = request.form + rule = json.loads(body.get("rule")) + pp(f"{'=' * 50}") + pp("Rule name:") + pp(rule) + + pp(f"{'-' * 50}") + target = json.loads(body.get("target")) + pp("Rule target:") + pp(target) + + pp(f"{'-' * 50}") + credentials = json.loads(body.get("credentials")) + pp("Rule credentials:") + pp(credentials) + + pp(f"{'=' * 50}") + return Response("True") + + +run_simple("0.0.0.0", 8080, application) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/cmd/__init__.py b/libs/skyline-policy-manager/src/skyline_policy_manager/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/cmd/manage.py b/libs/skyline-policy-manager/src/skyline_policy_manager/cmd/manage.py new file mode 100644 index 0000000..8b9dd9a --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/cmd/manage.py @@ -0,0 +1,284 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import json +from importlib.metadata import entry_points +from logging import StreamHandler +from pathlib import Path +from typing import Callable, Dict, Iterable, List, Union + +import click +from oslo_policy.policy import DocumentedRuleDefault, RuleDefault # type: ignore +from skyline_log import LOG, setup as log_setup + +from skyline_policy_manager.policies import get_service_rules +from skyline_policy_manager.policies.base import APIRule, Rule + +DEBUG = False +POLICY_NS = "oslo.policy.policies" +SUPPORTED_SERVICE_EPS = { + # openstack_service: [, ,] + "cinder": ["cinder"], + "glance": ["glance"], + "heat": ["heat"], + "ironic": ["ironic.api", "ironic_inspector.api"], + "keystone": ["keystone"], + "neutron": ["neutron", "neutron-fwaas", "neutron-vpnaas"], + "nova": ["nova"], + "octavia": ["octavia"], + "panko": ["panko"], + "placement": ["placement"], +} + +OSRules = Iterable[Union[DocumentedRuleDefault, RuleDefault]] + + +def load_list_rules_funcs( + namespace: str, + service_eps: Dict[str, List[str]], +) -> Dict[str, Callable[[], OSRules]]: + eps = set(entry_points()[namespace]) + supported_eps = set() + for ep_names in service_eps.values(): + supported_eps.update(ep_names) + return {ep.name: ep.load() for ep in eps if ep.name in supported_eps} + + +def load_list_rules_func(namespace: str, service_ep: str) -> Union[None, Callable[[], OSRules]]: + eps = set(entry_points()[namespace]) + for ep in eps: + if ep.name == service_ep: + return ep.load() + + return None + + +def comparison_rules( + service: str, + rule: Union[Rule, APIRule], + os_rule: Union[Rule, APIRule], +) -> None: + if isinstance(rule, APIRule) and isinstance(os_rule, APIRule): + if rule.scope_types != os_rule.scope_types: + LOG.error( + f'\nService "{service}" rule "{rule.name}" scope_types is {rule.scope_types},\n' + f"which is different from os_rule {os_rule.scope_types}.\n", + ) + if rule.operations != os_rule.operations: + LOG.error( + f'\nService "{service}" rule "{rule.name}" operations is {rule.operations},\n' + f"which is different from os_rule {os_rule.operations}.\n", + ) + elif (isinstance(rule, Rule) and isinstance(os_rule, APIRule)) or ( + isinstance(rule, APIRule) and isinstance(os_rule, Rule) + ): + LOG.warning( + f'\nService "{service}" rule "{rule.name}" is {rule.__class__},\n' + f"which is different from os_rule {os_rule.__class__}.\n", + ) + elif isinstance(rule, Rule) and isinstance(os_rule, Rule): + pass + else: + LOG.error(f'\nService "{service}" rule "{rule.name}" is unknown class type.\n') + + +@click.group(name="skyline-policy-manager", help="Policy manager command line.") +@click.option("--debug", is_flag=True, default=False, help="Output more info.") +def policy_manager(debug: bool) -> None: + global DEBUG + DEBUG = debug + log_setup(StreamHandler(), debug=DEBUG, colorize=True, level="INFO") + + +@click.command(help="Generate sample policy yaml file.") +@click.option("--dir", help='Directory of policy file.(default: "./tmp")', default="./tmp") +def generate_sample(dir: str) -> None: + list_rules_funcs = load_list_rules_funcs(POLICY_NS, SUPPORTED_SERVICE_EPS) + + rule_map = {} + for service, eps in SUPPORTED_SERVICE_EPS.items(): + rules = [] + api_rules = [] + for ep in eps: + ep_rules = list_rules_funcs.get(ep, lambda: [])() + for rule in ep_rules: + if isinstance(rule, DocumentedRuleDefault): + api_rules.append(APIRule.from_oslo(rule)) + elif isinstance(rule, RuleDefault): + rules.append(Rule.from_oslo(rule)) + + rule_map[service] = {"rules": rules, "api_rules": api_rules} + + for service, item in rule_map.items(): + dir_path = Path(dir).joinpath(service) + dir_path.mkdir(mode=0o755, parents=True, exist_ok=True) + file_path = dir_path.joinpath("policy.yaml.sample") + with open(file_path, "w") as f: + f.write(f"{'#' * 20}\n# {service}\n{'#' * 20}\n\n") + for rule in item.get("rules", []): + f.writelines(rule.format_into_yaml()) + for rule in item.get("api_rules", []): + f.writelines(rule.format_into_yaml()) + + LOG.info("Generate sample policy successful") + + +@click.command(help="Generate policy yaml file.") +@click.option("--dir", help='Directory of policy file.(default: "./tmp")', default="./tmp") +@click.option("--desc", help="Description of the generated policy file.", default="") +def generate_conf(dir: str, desc: str) -> None: + for service, rules in get_service_rules().items(): + dir_path = Path(dir).joinpath(service) + dir_path.mkdir(mode=0o755, parents=True, exist_ok=True) + file_path = dir_path.joinpath("policy.yaml") + with open(file_path, "w") as f: + f.write(f"{'#' * 20}\n# {service}\n{'#' * 20}\n") + f.write(f"# {desc}\n\n") + for rule in rules: + f.writelines(rule.format_into_yaml()) + + LOG.info("Generate policy successful") + + +@click.command(help="Generate service rule code.") +@click.argument("entry_point") +def generate_rule(entry_point: str) -> None: + ep_rules_func = load_list_rules_func(POLICY_NS, entry_point) + if ep_rules_func is None: + raise Exception( + f"Not found entry point '{entry_point}' in oslo.policy.policies namespace.", + ) + + ep_rules = [item for item in ep_rules_func()] + + rules = [] + api_rules = [] + for rule in ep_rules: + if isinstance(rule, DocumentedRuleDefault): + api_rules.append(APIRule.from_oslo(rule)) + elif isinstance(rule, RuleDefault): + rules.append(Rule.from_oslo(rule)) + + header_str = """ +from . import base + +list_rules = (""" + print(header_str) + + rule_format_str = ( + " base.Rule(\n" + " name={name},\n" + " check_str=({check_str}),\n" + " description={description},\n" + " )," + ) + for r in rules: + print( + rule_format_str.format( + name=json.dumps(r.name), + check_str=json.dumps(r.check_str), + description=json.dumps(r.description), + ), + ) + + apirule_format_str = ( + " base.APIRule(\n" + " name={name},\n" + " check_str=({check_str}),\n" + " description={description},\n" + " scope_types={scope_types},\n" + " operations={operations},\n" + " )," + ) + for r in api_rules: + print( + apirule_format_str.format( + name=json.dumps(r.name), + check_str=json.dumps(r.check_str), + description=json.dumps(r.description), + scope_types=json.dumps(r.scope_types), + operations=json.dumps(r.operations), + ), + ) + + footer_str = """) + +__all__ = ("list_rules",) +""" + print(footer_str) + + LOG.info("Generate service rule code successful") + + +@click.command(help="Validate all policy rules.") +@click.option("--diff", help="Output policy rule diff info.", is_flag=True, default=False) +def validate(diff: bool) -> None: + list_rules_funcs = load_list_rules_funcs(POLICY_NS, SUPPORTED_SERVICE_EPS) + + os_rule_map = {} + for service, eps in SUPPORTED_SERVICE_EPS.items(): + service_rules = {} + for ep in eps: + ep_rules = list_rules_funcs.get(ep, lambda: [])() + for rule in ep_rules: + if rule.name in service_rules: + LOG.error( + f'Service "{service}" entry point "{ep}" has duplicate rules ' + f'"{rule.name}", please check source code of {service} service.', + ) + if isinstance(rule, DocumentedRuleDefault): + service_rules[rule.name] = APIRule.from_oslo(rule) + elif isinstance(rule, RuleDefault): + service_rules[rule.name] = Rule.from_oslo(rule) + + if not service_rules: + LOG.warning( + f'Service "{service}" does not load any rules, please check whether the ' + f"service package is installed (pip list).", + ) + os_rule_map[service] = service_rules + + for service, rules in get_service_rules().items(): + for r in rules: + os_rule = os_rule_map.get(service, {}).get(r.name) + if os_rule is None: + LOG.warning( + f'Rule "{r.name}" is not found in service "{service}", if it\'s deprecated, ' + f"please remove.", + ) + else: + if diff: + LOG.info( + f'\nService "{service}" rule "{r.name}" compare results:\n' + f'{"OpenStack":10}: {os_rule.check_str}\n{"Custom":10}: {r.check_str}\n', + ) + comparison_rules(service, r, os_rule) + + unmanaged_rules = set(os_rule_map.get(service, {}).keys()) - set( + [r.name for r in rules], + ) + for r in unmanaged_rules: + LOG.error(f"Rule {r} is unmanaged, please add it in '{service}' service") + + LOG.info("Validate policy completed") + + +def main(): + policy_manager.add_command(generate_sample) + policy_manager.add_command(generate_conf) + policy_manager.add_command(generate_rule) + policy_manager.add_command(validate) + policy_manager() diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/__init__.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/__init__.py new file mode 100644 index 0000000..a6a3117 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/__init__.py @@ -0,0 +1,38 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from importlib import import_module +from os import path +from pkgutil import iter_modules +from typing import Dict, List, Union + +from .base import APIRule, Rule + +LIST_RULES_FUNC_NAME = "list_rules" + + +def get_service_rules() -> Dict[str, List[Union[Rule, APIRule]]]: + service_rules = {} + current_path = path.dirname(path.abspath(__file__)) + for m in iter_modules(path=[current_path]): + if m.name in ["base"] or m.ispkg: + continue + + module = import_module(f"{__package__}.{m.name}") + service_rules[m.name] = getattr(module, LIST_RULES_FUNC_NAME, []) + + return service_rules + + +__all__ = ("get_service_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/base.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/base.py new file mode 100644 index 0000000..fe3c261 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/base.py @@ -0,0 +1,121 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import List + +from oslo_policy import _parser # type: ignore +from oslo_policy.policy import DocumentedRuleDefault, RuleDefault # type: ignore + +from skyline_policy_manager.schema import Operation, OperationsSchema, ScopeTypesSchema + + +class Rule: + def __init__( + self, + name: str, + check_str: str, + description: str, + basic_check_str: str = "", + ) -> None: + self.name = name + self.check_str = check_str + self.check = _parser.parse_rule(self.check_str) + self.description = description or "No description" + self.basic_check_str = basic_check_str or self.check_str + self.basic_check = _parser.parse_rule(self.basic_check_str) + + def __str__(self) -> str: + return f'"{self.name}": "{self.check_str}"' + + def __repr__(self) -> str: + return f"{self.__class__.__qualname__}(name='{self.name}', check_str='{self.check_str}')" + + def __eq__(self, other: object) -> bool: + if isinstance(other, Rule) and isinstance(self, Rule): + return (self.name, self.check_str) == (other.name, other.check_str) + return False + + def format_into_yaml(self) -> str: + desc = f"# {self.description}\n" + text = f"{desc}{str(self)}\n\n" + + return text + + @classmethod + def from_oslo(cls, rule: RuleDefault): + description = rule.description or "" + description = description.replace("\n", "\n#") + return cls(name=rule.name, check_str=rule.check_str, description=description) + + +class APIRule(Rule): + def __init__( + self, + name: str, + check_str: str, + description: str, + scope_types: List[str], + operations: List[Operation], + basic_check_str: str = "", + ) -> None: + super().__init__(name, check_str, description, basic_check_str) + + ScopeTypesSchema.parse_obj(scope_types) + self.scope_types = scope_types + + OperationsSchema.parse_obj(operations) + self.operations = operations + + def format_into_yaml(self) -> str: + op_list = [ + f'# {operation.get("method"):8}{operation.get("path")}\n' + for operation in self.operations + ] + op = "".join(op_list) + scope = f"# Intended scope(s): {self.scope_types}\n" + + desc = f"# {self.description}\n" + text = f"{desc}{op}{scope}{str(self)}\n\n" + + return text + + @classmethod + def from_oslo(cls, rule: DocumentedRuleDefault): + description = rule.description or "" + description = description.replace("\n", "\n#") + if isinstance(rule.scope_types, list): + scope_types = [item for item in rule.scope_types] + else: + scope_types = ["project"] + operations = [] + for operation in rule.operations: + method = operation.get("method") + if isinstance(method, list): + for i in method: + operations.append(Operation(method=i.upper(), path=operation.get("path", ""))) + elif isinstance(method, str): + operations.append( + Operation(method=method.upper(), path=operation.get("path", "")), + ) + else: + operations.append(Operation(method="GET", path=operation.get("path", ""))) + return cls( + name=rule.name, + check_str=rule.check_str, + description=description, + scope_types=scope_types, + operations=operations, + ) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/cinder.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/cinder.py new file mode 100644 index 0000000..7f887b1 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/cinder.py @@ -0,0 +1,1245 @@ +from . import base + +list_rules = ( + base.Rule( + name="context_is_admin", + check_str=("role:admin"), + description="Decides what is required for the 'is_admin:True' check to succeed.", + ), + base.Rule( + name="admin_or_owner", + check_str=( + "is_admin:True or (role:admin and is_admin_project:True) or " + "project_id:%(project_id)s" + ), + description="Default rule for most non-Admin APIs.", + ), + base.Rule( + name="admin_api", + check_str=("is_admin:True or (role:admin and is_admin_project:True)"), + description="Default rule for most Admin APIs.", + ), + base.Rule( + name="system_or_domain_or_project_admin", + check_str=( + "(role:admin and system_scope:all) " + "or (role:admin and domain_id:%(domain_id)s) " + "or (role:admin and project_id:%(project_id)s)" + ), + description="Default rule for admins of cloud, domain or a project.", + ), + base.APIRule( + name="volume:attachment_create", + check_str=(""), + description="Create attachment.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/attachments"}], + ), + base.APIRule( + name="volume:attachment_update", + check_str=("rule:admin_or_owner"), + description="Update attachment.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/attachments/{attachment_id}"}], + ), + base.APIRule( + name="volume:attachment_delete", + check_str=("rule:admin_or_owner"), + description="Delete attachment.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/attachments/{attachment_id}"}], + ), + base.APIRule( + name="volume:attachment_complete", + check_str=("rule:admin_or_owner"), + description="Mark a volume attachment process as completed (in-use)", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/attachments/{attachment_id}/action (os-complete)"}, + ], + ), + base.APIRule( + name="volume:multiattach_bootable_volume", + check_str=("rule:admin_or_owner"), + description="Allow multiattach of bootable volumes.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/attachments"}], + ), + base.APIRule( + name="message:get_all", + check_str=("rule:admin_or_owner"), + description="List messages.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/messages"}], + ), + base.APIRule( + name="message:get", + check_str=("rule:admin_or_owner"), + description="Show message.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/messages/{message_id}"}], + ), + base.APIRule( + name="message:delete", + check_str=("rule:admin_or_owner"), + description="Delete message.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/messages/{message_id}"}], + ), + base.APIRule( + name="clusters:get_all", + check_str=("rule:admin_api"), + description="List clusters.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/clusters"}, + {"method": "GET", "path": "/clusters/detail"}, + ], + ), + base.APIRule( + name="clusters:get", + check_str=("rule:admin_api"), + description="Show cluster.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/clusters/{cluster_id}"}], + ), + base.APIRule( + name="clusters:update", + check_str=("rule:admin_api"), + description="Update cluster.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/clusters/{cluster_id}"}], + ), + base.APIRule( + name="workers:cleanup", + check_str=("rule:admin_api"), + description="Clean up workers.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/workers/cleanup"}], + ), + base.APIRule( + name="volume:get_snapshot_metadata", + check_str=("rule:admin_or_owner"), + description="Show snapshot's metadata or one specified metadata with a given key.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/snapshots/{snapshot_id}/metadata"}, + {"method": "GET", "path": "/snapshots/{snapshot_id}/metadata/{key}"}, + ], + ), + base.APIRule( + name="volume:update_snapshot_metadata", + check_str=("rule:admin_or_owner"), + description="Update snapshot's metadata or one specified metadata with a given key.", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/snapshots/{snapshot_id}/metadata"}, + {"method": "PUT", "path": "/snapshots/{snapshot_id}/metadata/{key}"}, + ], + ), + base.APIRule( + name="volume:delete_snapshot_metadata", + check_str=("rule:admin_or_owner"), + description="Delete snapshot's specified metadata with a given key.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/snapshots/{snapshot_id}/metadata/{key}"}], + ), + base.APIRule( + name="volume:get_all_snapshots", + check_str=("rule:admin_or_owner"), + description="List snapshots.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/snapshots"}, + {"method": "GET", "path": "/snapshots/detail"}, + ], + ), + base.APIRule( + name="volume_extension:extended_snapshot_attributes", + check_str=("rule:admin_or_owner"), + description="List or show snapshots with extended attributes.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/snapshots/{snapshot_id}"}, + {"method": "GET", "path": "/snapshots/detail"}, + ], + ), + base.APIRule( + name="volume:create_snapshot", + check_str=("rule:admin_or_owner"), + description="Create snapshot.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/snapshots"}], + ), + base.APIRule( + name="volume:get_snapshot", + check_str=("rule:admin_or_owner"), + description="Show snapshot.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/snapshots/{snapshot_id}"}], + ), + base.APIRule( + name="volume:update_snapshot", + check_str=("rule:admin_or_owner"), + description="Update snapshot.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/snapshots/{snapshot_id}"}], + ), + base.APIRule( + name="volume:delete_snapshot", + check_str=("rule:admin_or_owner"), + description="Delete snapshot.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/snapshots/{snapshot_id}"}], + ), + base.APIRule( + name="volume_extension:snapshot_admin_actions:reset_status", + check_str=("rule:admin_api"), + description="Reset status of a snapshot.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/snapshots/{snapshot_id}/action (os-reset_status)"}, + ], + ), + base.APIRule( + name="snapshot_extension:snapshot_actions:update_snapshot_status", + check_str=(""), + description="Update database fields of snapshot.", + scope_types=["project"], + operations=[ + { + "method": "POST", + "path": "/snapshots/{snapshot_id}/action (update_snapshot_status)", + }, + ], + ), + base.APIRule( + name="volume_extension:snapshot_admin_actions:force_delete", + check_str=("rule:admin_api"), + description="Force delete a snapshot.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/snapshots/{snapshot_id}/action (os-force_delete)"}, + ], + ), + base.APIRule( + name="snapshot_extension:list_manageable", + check_str=("rule:admin_api"), + description="List (in detail) of snapshots which are available to manage.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/manageable_snapshots"}, + {"method": "GET", "path": "/manageable_snapshots/detail"}, + ], + ), + base.APIRule( + name="snapshot_extension:snapshot_manage", + check_str=("rule:admin_api"), + description="Manage an existing snapshot.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/manageable_snapshots"}], + ), + base.APIRule( + name="snapshot_extension:snapshot_unmanage", + check_str=("rule:admin_api"), + description="Stop managing a snapshot.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/snapshots/{snapshot_id}/action (os-unmanage)"}], + ), + base.APIRule( + name="backup:get_all", + check_str=("rule:admin_or_owner"), + description="List backups.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/backups"}, + {"method": "GET", "path": "/backups/detail"}, + ], + ), + base.APIRule( + name="backup:backup_project_attribute", + check_str=("rule:admin_api"), + description="List backups or show backup with project attributes.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/backups/{backup_id}"}, + {"method": "GET", "path": "/backups/detail"}, + ], + ), + base.APIRule( + name="backup:create", + check_str=(""), + description="Create backup.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/backups"}], + ), + base.APIRule( + name="backup:get", + check_str=("rule:admin_or_owner"), + description="Show backup.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/backups/{backup_id}"}], + ), + base.APIRule( + name="backup:update", + check_str=("rule:admin_or_owner"), + description="Update backup.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/backups/{backup_id}"}], + ), + base.APIRule( + name="backup:delete", + check_str=("rule:admin_or_owner"), + description="Delete backup.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/backups/{backup_id}"}], + ), + base.APIRule( + name="backup:restore", + check_str=("rule:admin_or_owner"), + description="Restore backup.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/backups/{backup_id}/restore"}], + ), + base.APIRule( + name="backup:backup-import", + check_str=("rule:admin_api"), + description="Import backup.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/backups/{backup_id}/import_record"}], + ), + base.APIRule( + name="backup:export-import", + check_str=("rule:admin_api"), + description="Export backup.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/backups/{backup_id}/export_record"}], + ), + base.APIRule( + name="volume_extension:backup_admin_actions:reset_status", + check_str=("rule:admin_api"), + description="Reset status of a backup.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/backups/{backup_id}/action (os-reset_status)"}], + ), + base.APIRule( + name="volume_extension:backup_admin_actions:force_delete", + check_str=("rule:admin_api"), + description="Force delete a backup.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/backups/{backup_id}/action (os-force_delete)"}], + ), + base.APIRule( + name="group:get_all", + check_str=("rule:admin_or_owner"), + description="List groups.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/groups"}, + {"method": "GET", "path": "/groups/detail"}, + ], + ), + base.APIRule( + name="group:create", + check_str=(""), + description="Create group.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/groups"}], + ), + base.APIRule( + name="group:get", + check_str=("rule:admin_or_owner"), + description="Show group.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/groups/{group_id}"}], + ), + base.APIRule( + name="group:update", + check_str=("rule:admin_or_owner"), + description="Update group.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/groups/{group_id}"}], + ), + base.APIRule( + name="group:group_project_attribute", + check_str=("rule:admin_api"), + description="List groups or show group with project attributes.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/groups/{group_id}"}, + {"method": "GET", "path": "/groups/detail"}, + ], + ), + base.APIRule( + name="group:group_types_manage", + check_str=("rule:admin_api"), + description="Create, update or delete a group type.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/group_types/"}, + {"method": "PUT", "path": "/group_types/{group_type_id}"}, + {"method": "DELETE", "path": "/group_types/{group_type_id}"}, + ], + ), + base.APIRule( + name="group:access_group_types_specs", + check_str=("rule:admin_api"), + description="Show group type with type specs attributes.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/group_types/{group_type_id}"}], + ), + base.APIRule( + name="group:group_types_specs", + check_str=("rule:admin_api"), + description="Create, show, update and delete group type spec.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/group_types/{group_type_id}/group_specs/{g_spec_id}"}, + {"method": "GET", "path": "/group_types/{group_type_id}/group_specs"}, + {"method": "POST", "path": "/group_types/{group_type_id}/group_specs"}, + {"method": "PUT", "path": "/group_types/{group_type_id}/group_specs/{g_spec_id}"}, + {"method": "DELETE", "path": "/group_types/{group_type_id}/group_specs/{g_spec_id}"}, + ], + ), + base.APIRule( + name="group:get_all_group_snapshots", + check_str=("rule:admin_or_owner"), + description="List group snapshots.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/group_snapshots"}, + {"method": "GET", "path": "/group_snapshots/detail"}, + ], + ), + base.APIRule( + name="group:create_group_snapshot", + check_str=(""), + description="Create group snapshot.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/group_snapshots"}], + ), + base.APIRule( + name="group:get_group_snapshot", + check_str=("rule:admin_or_owner"), + description="Show group snapshot.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/group_snapshots/{group_snapshot_id}"}], + ), + base.APIRule( + name="group:delete_group_snapshot", + check_str=("rule:admin_or_owner"), + description="Delete group snapshot.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/group_snapshots/{group_snapshot_id}"}], + ), + base.APIRule( + name="group:update_group_snapshot", + check_str=("rule:admin_or_owner"), + description="Update group snapshot.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/group_snapshots/{group_snapshot_id}"}], + ), + base.APIRule( + name="group:group_snapshot_project_attribute", + check_str=("rule:admin_api"), + description="List group snapshots or show group snapshot with project attributes.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/group_snapshots/{group_snapshot_id}"}, + {"method": "GET", "path": "/group_snapshots/detail"}, + ], + ), + base.APIRule( + name="group:reset_group_snapshot_status", + check_str=("rule:admin_api"), + description="Reset status of group snapshot.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/group_snapshots/{g_snapshot_id}/action (reset_status)"}, + ], + ), + base.APIRule( + name="group:delete", + check_str=("rule:admin_or_owner"), + description="Delete group.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/groups/{group_id}/action (delete)"}], + ), + base.APIRule( + name="group:reset_status", + check_str=("rule:admin_api"), + description="Reset status of group.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/groups/{group_id}/action (reset_status)"}], + ), + base.APIRule( + name="group:enable_replication", + check_str=("rule:admin_or_owner"), + description="Enable replication.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/groups/{group_id}/action (enable_replication)"}], + ), + base.APIRule( + name="group:disable_replication", + check_str=("rule:admin_or_owner"), + description="Disable replication.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/groups/{group_id}/action (disable_replication)"}, + ], + ), + base.APIRule( + name="group:failover_replication", + check_str=("rule:admin_or_owner"), + description="Fail over replication.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/groups/{group_id}/action (failover_replication)"}, + ], + ), + base.APIRule( + name="group:list_replication_targets", + check_str=("rule:admin_or_owner"), + description="List failover replication.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/groups/{group_id}/action (list_replication_targets)"}, + ], + ), + base.APIRule( + name="volume_extension:qos_specs_manage:get_all", + check_str=("rule:admin_api"), + description="List qos specs or list all associations.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/qos-specs"}, + {"method": "GET", "path": "/qos-specs/{qos_id}/associations"}, + ], + ), + base.APIRule( + name="volume_extension:qos_specs_manage:get", + check_str=("rule:admin_api"), + description="Show qos specs.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/qos-specs/{qos_id}"}], + ), + base.APIRule( + name="volume_extension:qos_specs_manage:create", + check_str=("rule:admin_api"), + description="Create qos specs.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/qos-specs"}], + ), + base.APIRule( + name="volume_extension:qos_specs_manage:update", + check_str=("rule:admin_api"), + description="Update qos specs (including updating association).", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/qos-specs/{qos_id}"}, + {"method": "GET", "path": "/qos-specs/{qos_id}/disassociate_all"}, + {"method": "GET", "path": "/qos-specs/{qos_id}/associate"}, + {"method": "GET", "path": "/qos-specs/{qos_id}/disassociate"}, + ], + ), + base.APIRule( + name="volume_extension:qos_specs_manage:delete", + check_str=("rule:admin_api"), + description="delete qos specs or unset one specified qos key.", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/qos-specs/{qos_id}"}, + {"method": "PUT", "path": "/qos-specs/{qos_id}/delete_keys"}, + ], + ), + base.APIRule( + name="volume_extension:quota_classes", + check_str=("rule:admin_api"), + description="Show or update project quota class.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/os-quota-class-sets/{project_id}"}, + {"method": "PUT", "path": "/os-quota-class-sets/{project_id}"}, + ], + ), + base.APIRule( + name="volume_extension:quotas:show", + check_str=("rule:admin_or_owner"), + description="Show project quota (including usage and default).", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/os-quota-sets/{project_id}"}, + {"method": "GET", "path": "/os-quota-sets/{project_id}/default"}, + {"method": "GET", "path": "/os-quota-sets/{project_id}?usage=True"}, + ], + ), + base.APIRule( + name="volume_extension:quotas:update", + check_str=("rule:admin_api"), + description="Update project quota.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/os-quota-sets/{project_id}"}], + ), + base.APIRule( + name="volume_extension:quotas:delete", + check_str=("rule:admin_api"), + description="Delete project quota.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/os-quota-sets/{project_id}"}], + ), + base.APIRule( + name="volume_extension:capabilities", + check_str=("rule:admin_api"), + description="Show backend capabilities.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/capabilities/{host_name}"}], + ), + base.APIRule( + name="volume_extension:services:index", + check_str=("rule:admin_api"), + description="List all services.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/os-services"}], + ), + base.APIRule( + name="volume_extension:services:update", + check_str=("rule:admin_api"), + description="Update service, including failover_host, thaw, freeze, " + "disable, enable, set-log and get-log actions.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/os-services/{action}"}], + ), + base.APIRule( + name="volume:freeze_host", + check_str=("rule:admin_api"), + description="Freeze a backend host.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/os-services/freeze"}], + ), + base.APIRule( + name="volume:thaw_host", + check_str=("rule:admin_api"), + description="Thaw a backend host.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/os-services/thaw"}], + ), + base.APIRule( + name="volume:failover_host", + check_str=("rule:admin_api"), + description="Failover a backend host.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/os-services/failover_host"}], + ), + base.APIRule( + name="scheduler_extension:scheduler_stats:get_pools", + check_str=("rule:admin_api"), + description="List all backend pools.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/scheduler-stats/get_pools"}], + ), + base.APIRule( + name="volume_extension:hosts", + check_str=("rule:admin_api"), + description="List, update or show hosts for a project.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/os-hosts"}, + {"method": "PUT", "path": "/os-hosts/{host_name}"}, + {"method": "GET", "path": "/os-hosts/{host_id}"}, + ], + ), + base.APIRule( + name="limits_extension:used_limits", + check_str=("rule:admin_or_owner"), + description="Show limits with used limit attributes.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/limits"}], + ), + base.APIRule( + name="volume_extension:list_manageable", + check_str=("rule:admin_api"), + description="List (in detail) of volumes which are available to manage.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/manageable_volumes"}, + {"method": "GET", "path": "/manageable_volumes/detail"}, + ], + ), + base.APIRule( + name="volume_extension:volume_manage", + check_str=("rule:admin_api"), + description="Manage existing volumes.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/manageable_volumes"}], + ), + base.APIRule( + name="volume_extension:volume_unmanage", + check_str=("rule:admin_api"), + description="Stop managing a volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-unmanage)"}], + ), + base.APIRule( + name="volume_extension:types_manage", + check_str=("rule:admin_api"), + description="Create, update and delete volume type.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/types"}, + {"method": "PUT", "path": "/types"}, + {"method": "DELETE", "path": "/types"}, + ], + ), + base.APIRule( + name="volume_extension:type_get", + check_str=(""), + description="Get one specific volume type.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/types/{type_id}"}], + ), + base.APIRule( + name="volume_extension:type_get_all", + check_str=(""), + description="List volume types.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/types/"}], + ), + base.APIRule( + name="volume_extension:volume_type_encryption", + check_str=("rule:admin_api"), + description="Base policy for all volume type encryption type " + "operations. This can be used to set the policies for " + "a volume type's encryption type create, show, update, " + "and delete actions in one place, or any of those may be " + "set individually using the following policy targets for " + "finer grained control.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/types/{type_id}/encryption"}, + {"method": "PUT", "path": "/types/{type_id}/encryption/{encryption_id}"}, + {"method": "GET", "path": "/types/{type_id}/encryption"}, + {"method": "GET", "path": "/types/{type_id}/encryption/{key}"}, + {"method": "DELETE", "path": "/types/{type_id}/encryption/{encryption_id}"}, + ], + ), + base.APIRule( + name="volume_extension:volume_type_encryption:create", + check_str=("rule:volume_extension:volume_type_encryption"), + description="Create volume type encryption.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/types/{type_id}/encryption"}], + ), + base.APIRule( + name="volume_extension:volume_type_encryption:get", + check_str=("rule:volume_extension:volume_type_encryption"), + description="Show a volume type's encryption type, show an encryption specs item.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/types/{type_id}/encryption"}, + {"method": "GET", "path": "/types/{type_id}/encryption/{key}"}, + ], + ), + base.APIRule( + name="volume_extension:volume_type_encryption:update", + check_str=("rule:volume_extension:volume_type_encryption"), + description="Update volume type encryption.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/types/{type_id}/encryption/{encryption_id}"}], + ), + base.APIRule( + name="volume_extension:volume_type_encryption:delete", + check_str=("rule:volume_extension:volume_type_encryption"), + description="Delete volume type encryption.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/types/{type_id}/encryption/{encryption_id}"}], + ), + base.APIRule( + name="volume_extension:access_types_extra_specs", + check_str=("rule:admin_api"), + description="List or show volume type with access type extra specs attribute.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/types/{type_id}"}, + {"method": "GET", "path": "/types"}, + ], + ), + base.APIRule( + name="volume_extension:access_types_qos_specs_id", + check_str=("rule:admin_api"), + description="List or show volume type with access type qos specs id attribute.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/types/{type_id}"}, + {"method": "GET", "path": "/types"}, + ], + ), + base.APIRule( + name="volume_extension:volume_type_access", + check_str=("rule:admin_or_owner"), + description="Volume type access related APIs.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/types"}, + {"method": "GET", "path": "/types/detail"}, + {"method": "GET", "path": "/types/{type_id}"}, + {"method": "POST", "path": "/types"}, + ], + ), + base.APIRule( + name="volume_extension:volume_type_access:addProjectAccess", + check_str=("rule:admin_api"), + description="Add volume type access for project.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/types/{type_id}/action (addProjectAccess)"}], + ), + base.APIRule( + name="volume_extension:volume_type_access:removeProjectAccess", + check_str=("rule:admin_api"), + description="Remove volume type access for project.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/types/{type_id}/action (removeProjectAccess)"}], + ), + base.APIRule( + name="volume:extend", + check_str=("rule:admin_or_owner"), + description="Extend a volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-extend)"}], + ), + base.APIRule( + name="volume:extend_attached_volume", + check_str=("rule:admin_or_owner"), + description="Extend a attached volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-extend)"}], + ), + base.APIRule( + name="volume:revert_to_snapshot", + check_str=("rule:admin_or_owner"), + description="Revert a volume to a snapshot.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (revert)"}], + ), + base.APIRule( + name="volume_extension:volume_admin_actions:reset_status", + check_str=("rule:admin_api"), + description="Reset status of a volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-reset_status)"}], + ), + base.APIRule( + name="volume:retype", + check_str=("rule:admin_or_owner"), + description="Retype a volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-retype)"}], + ), + base.APIRule( + name="volume:update_readonly_flag", + check_str=("rule:admin_or_owner"), + description="Update a volume's readonly flag.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-update_readonly_flag)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_admin_actions:force_delete", + check_str=("rule:admin_api"), + description="Force delete a volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-force_delete)"}], + ), + base.APIRule( + name="volume_extension:volume_actions:upload_public", + check_str=("rule:admin_api"), + description="Upload a volume to image with public visibility.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-volume_upload_image)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_actions:upload_image", + check_str=("rule:admin_or_owner"), + description="Upload a volume to image.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-volume_upload_image)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_admin_actions:force_detach", + check_str=("rule:admin_api"), + description="Force detach a volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-force_detach)"}], + ), + base.APIRule( + name="volume_extension:volume_admin_actions:migrate_volume", + check_str=("rule:admin_api"), + description="migrate a volume to a specified host.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-migrate_volume)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_admin_actions:migrate_volume_completion", + check_str=("rule:admin_api"), + description="Complete a volume migration.", + scope_types=["project"], + operations=[ + { + "method": "POST", + "path": "/volumes/{volume_id}/action (os-migrate_volume_completion)", + }, + ], + ), + base.APIRule( + name="volume_extension:volume_actions:initialize_connection", + check_str=("rule:admin_or_owner"), + description="Initialize volume attachment.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-initialize_connection)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_actions:terminate_connection", + check_str=("rule:admin_or_owner"), + description="Terminate volume attachment.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-terminate_connection)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_actions:roll_detaching", + check_str=("rule:admin_or_owner"), + description="Roll back volume status to 'in-use'.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-roll_detaching)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_actions:reserve", + check_str=("rule:admin_or_owner"), + description="Mark volume as reserved.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-reserve)"}], + ), + base.APIRule( + name="volume_extension:volume_actions:unreserve", + check_str=("rule:admin_or_owner"), + description="Unmark volume as reserved.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-unreserve)"}], + ), + base.APIRule( + name="volume_extension:volume_actions:begin_detaching", + check_str=("rule:admin_or_owner"), + description="Begin detach volumes.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-begin_detaching)"}, + ], + ), + base.APIRule( + name="volume_extension:volume_actions:attach", + check_str=("rule:admin_or_owner"), + description="Add attachment metadata.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-attach)"}], + ), + base.APIRule( + name="volume_extension:volume_actions:detach", + check_str=("rule:admin_or_owner"), + description="Clear attachment metadata.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/action (os-detach)"}], + ), + base.APIRule( + name="volume:get_all_transfers", + check_str=("rule:admin_or_owner"), + description="List volume transfer.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/os-volume-transfer"}, + {"method": "GET", "path": "/os-volume-transfer/detail"}, + {"method": "GET", "path": "/volume_transfers"}, + {"method": "GET", "path": "/volume-transfers/detail"}, + ], + ), + base.APIRule( + name="volume:create_transfer", + check_str=("rule:admin_or_owner"), + description="Create a volume transfer.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/os-volume-transfer"}, + {"method": "POST", "path": "/volume_transfers"}, + ], + ), + base.APIRule( + name="volume:get_transfer", + check_str=("rule:admin_or_owner"), + description="Show one specified volume transfer.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/os-volume-transfer/{transfer_id}"}, + {"method": "GET", "path": "/volume-transfers/{transfer_id}"}, + ], + ), + base.APIRule( + name="volume:accept_transfer", + check_str=(""), + description="Accept a volume transfer.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/os-volume-transfer/{transfer_id}/accept"}, + {"method": "POST", "path": "/volume-transfers/{transfer_id}/accept"}, + ], + ), + base.APIRule( + name="volume:delete_transfer", + check_str=("rule:admin_or_owner"), + description="Delete volume transfer.", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/os-volume-transfer/{transfer_id}"}, + {"method": "DELETE", "path": "/volume-transfers/{transfer_id}"}, + ], + ), + base.APIRule( + name="volume:get_volume_metadata", + check_str=("rule:admin_or_owner"), + description="Show volume's metadata or one specified metadata with a given key.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/volumes/{volume_id}/metadata"}, + {"method": "GET", "path": "/volumes/{volume_id}/metadata/{key}"}, + ], + ), + base.APIRule( + name="volume:create_volume_metadata", + check_str=("rule:admin_or_owner"), + description="Create volume metadata.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes/{volume_id}/metadata"}], + ), + base.APIRule( + name="volume:update_volume_metadata", + check_str=("rule:admin_or_owner"), + description="Update volume's metadata or one specified metadata with a given key.", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/volumes/{volume_id}/metadata"}, + {"method": "PUT", "path": "/volumes/{volume_id}/metadata/{key}"}, + ], + ), + base.APIRule( + name="volume:delete_volume_metadata", + check_str=("rule:admin_or_owner"), + description="Delete volume's specified metadata with a given key.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/volumes/{volume_id}/metadata/{key}"}], + ), + base.APIRule( + name="volume_extension:volume_image_metadata", + check_str=("rule:admin_or_owner"), + description="Volume's image metadata related operation, " + "create, delete, show and list.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/volumes/detail"}, + {"method": "GET", "path": "/volumes/{volume_id}"}, + {"method": "POST", "path": "/volumes/{volume_id}/action (os-set_image_metadata)"}, + {"method": "POST", "path": "/volumes/{volume_id}/action (os-unset_image_metadata)"}, + ], + ), + base.APIRule( + name="volume:update_volume_admin_metadata", + check_str=("rule:admin_api"), + description="Update volume admin metadata. " + "It's used in `attach` and `os-update_readonly_flag` APIs", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/volumes/{volume_id}/action (os-update_readonly_flag)"}, + {"method": "POST", "path": "/volumes/{volume_id}/action (os-attach)"}, + ], + ), + base.APIRule( + name="volume_extension:types_extra_specs:index", + check_str=("rule:admin_api"), + description="List type extra specs.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/types/{type_id}/extra_specs"}], + ), + base.APIRule( + name="volume_extension:types_extra_specs:create", + check_str=("rule:admin_api"), + description="Create type extra specs.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/types/{type_id}/extra_specs"}], + ), + base.APIRule( + name="volume_extension:types_extra_specs:show", + check_str=("rule:admin_api"), + description="Show one specified type extra specs.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/types/{type_id}/extra_specs/{extra_spec_key}"}], + ), + base.APIRule( + name="volume_extension:types_extra_specs:update", + check_str=("rule:admin_api"), + description="Update type extra specs.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/types/{type_id}/extra_specs/{extra_spec_key}"}], + ), + base.APIRule( + name="volume_extension:types_extra_specs:delete", + check_str=("rule:admin_api"), + description="Delete type extra specs.", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/types/{type_id}/extra_specs/{extra_spec_key}"}, + ], + ), + base.APIRule( + name="volume:create", + check_str=(""), + description="Create volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes"}], + ), + base.APIRule( + name="volume:create_from_image", + check_str=(""), + description="Create volume from image.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes"}], + ), + base.APIRule( + name="volume:get", + check_str=("rule:admin_or_owner"), + description="Show volume.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/volumes/{volume_id}"}], + ), + base.APIRule( + name="volume:get_all", + check_str=("rule:admin_or_owner"), + description="List volumes or get summary of volumes.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/volumes"}, + {"method": "GET", "path": "/volumes/detail"}, + {"method": "GET", "path": "/volumes/summary"}, + ], + ), + base.APIRule( + name="volume:update", + check_str=("rule:admin_or_owner"), + description="Update volume or update a volume's bootable status.", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/volumes"}, + {"method": "POST", "path": "/volumes/{volume_id}/action (os-set_bootable)"}, + ], + ), + base.APIRule( + name="volume:delete", + check_str=("rule:admin_or_owner"), + description="Delete volume.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/volumes/{volume_id}"}], + ), + base.APIRule( + name="volume:force_delete", + check_str=("rule:admin_api"), + description="Force Delete a volume.", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/volumes/{volume_id}"}], + ), + base.APIRule( + name="volume_extension:volume_host_attribute", + check_str=("rule:admin_api"), + description="List or show volume with host attribute.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/volumes/{volume_id}"}, + {"method": "GET", "path": "/volumes/detail"}, + ], + ), + base.APIRule( + name="volume_extension:volume_tenant_attribute", + check_str=("rule:admin_or_owner"), + description="List or show volume with tenant attribute.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/volumes/{volume_id}"}, + {"method": "GET", "path": "/volumes/detail"}, + ], + ), + base.APIRule( + name="volume_extension:volume_mig_status_attribute", + check_str=("rule:admin_api"), + description="List or show volume with migration status attribute.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/volumes/{volume_id}"}, + {"method": "GET", "path": "/volumes/detail"}, + ], + ), + base.APIRule( + name="volume_extension:volume_encryption_metadata", + check_str=("rule:admin_or_owner"), + description="Show volume's encryption metadata.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/volumes/{volume_id}/encryption"}, + {"method": "GET", "path": "/volumes/{volume_id}/encryption/{encryption_key}"}, + ], + ), + base.APIRule( + name="volume:multiattach", + check_str=("rule:admin_or_owner"), + description="Create multiattach capable volume.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/volumes"}], + ), + base.APIRule( + name="volume_extension:default_set_or_update", + check_str=("rule:system_or_domain_or_project_admin"), + description="Set or update default volume type.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/default-types"}], + ), + base.APIRule( + name="volume_extension:default_get", + check_str=("rule:system_or_domain_or_project_admin"), + description="Get default types.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/default-types/{project-id}"}], + ), + base.APIRule( + name="volume_extension:default_get_all", + check_str=("role:admin and system_scope:all"), + description="Get all default types. " + "WARNING: Changing this might open up too much " + "information regarding cloud deployment.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/default-types/"}], + ), + base.APIRule( + name="volume_extension:default_unset", + check_str=("rule:system_or_domain_or_project_admin"), + description="Unset default type.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/default-types/{project-id}"}], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/glance.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/glance.py new file mode 100644 index 0000000..e31d816 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/glance.py @@ -0,0 +1,381 @@ +from . import base + +list_rules = ( + base.Rule( + name="default", + check_str=(""), + description="Defines the default rule used for policies that " + "historically had an empty policy in the supplied " + "policy.json file.", + ), + base.Rule( + name="context_is_admin", + check_str=("role:admin"), + description="Defines the rule for the is_admin:True check.", + ), + base.Rule( + name="manage_image_cache", + check_str=("role:admin"), + description="Manage image cache", + ), + base.Rule( + name="metadef_default", + check_str=(""), + description="No description", + ), + base.Rule( + name="metadef_admin", + check_str=("role:admin"), + description="No description", + ), + base.Rule( + name="get_metadef_namespace", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="get_metadef_namespaces", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="modify_metadef_namespace", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="add_metadef_namespace", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="delete_metadef_namespace", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="get_metadef_object", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="get_metadef_objects", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="modify_metadef_object", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="add_metadef_object", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="delete_metadef_object", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="list_metadef_resource_types", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="get_metadef_resource_type", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="add_metadef_resource_type_association", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="remove_metadef_resource_type_association", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="get_metadef_property", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="get_metadef_properties", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="modify_metadef_property", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="add_metadef_property", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="remove_metadef_property", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="get_metadef_tag", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="get_metadef_tags", + check_str=("rule:metadef_default"), + description="No description", + ), + base.Rule( + name="modify_metadef_tag", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="add_metadef_tag", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="add_metadef_tags", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="delete_metadef_tag", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.Rule( + name="delete_metadef_tags", + check_str=("rule:metadef_admin"), + description="No description", + ), + base.APIRule( + name="add_image", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Create new image", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v2/images"}], + ), + base.APIRule( + name="delete_image", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Deletes the image", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="get_image", + check_str=( + "role:admin or (role:reader and (project_id:%(project_id)s or " + 'project_id:%(member_id)s or "community":%(visibility)s or ' + '"public":%(visibility)s)) ' + ), + description="Get specified image", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="get_images", + check_str=("role:admin or (role:reader and project_id:%(project_id)s)"), + description="Get all available images", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/images"}], + ), + base.APIRule( + name="modify_image", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Updates given image", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="publicize_image", + check_str=("role:admin"), + description="Publicize given image", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="communitize_image", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Communitize given image", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="download_image", + check_str=( + "role:admin or (role:member and (project_id:%(project_id)s or " + 'project_id:%(member_id)s or "community":%(visibility)s or ' + '"public":%(visibility)s)) ' + ), + description="Downloads given image", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/images/{image_id}/file"}], + ), + base.APIRule( + name="upload_image", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Uploads data to specified image", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/v2/images/{image_id}/file"}], + ), + base.APIRule( + name="delete_image_location", + check_str=("role:admin"), + description="Deletes the location of given image", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="get_image_location", + check_str=("role:admin or (role:reader and project_id:%(project_id)s)"), + description="Reads the location of the image", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="set_image_location", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Sets location URI to given image", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/v2/images/{image_id}"}], + ), + base.APIRule( + name="add_member", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Create image member", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v2/images/{image_id}/members"}], + ), + base.APIRule( + name="delete_member", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Delete image member", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/v2/images/{image_id}/members/{member_id}"}], + ), + base.APIRule( + name="get_member", + check_str=("role:admin or (role:reader and project_id:%(project_id)s)"), + description="Show image member details", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/images/{image_id}/members/{member_id}"}], + ), + base.APIRule( + name="get_members", + check_str=("role:admin or (role:reader and project_id:%(project_id)s)"), + description="List image members", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/images/{image_id}/members"}], + ), + base.APIRule( + name="modify_member", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Update image member", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/v2/images/{image_id}/members/{member_id}"}], + ), + base.APIRule( + name="deactivate", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Deactivate image", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v2/images/{image_id}/actions/deactivate"}], + ), + base.APIRule( + name="reactivate", + check_str=("role:admin or (role:member and project_id:%(project_id)s)"), + description="Reactivate image", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v2/images/{image_id}/actions/reactivate"}], + ), + base.APIRule( + name="copy_image", + check_str=("role:admin"), + description="Copy existing image to other stores", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v2/images/{image_id}/import"}], + ), + base.APIRule( + name="get_task", + check_str=("rule:default"), + description="Get an image task.\n#\n#This granular policy controls " + "access to tasks, both from the tasks API as well\n" + "#as internal locations in Glance that use tasks " + "(like import). Practically this\n#cannot be more " + "restrictive than the policy that controls import or " + "things will\n#break, and changing it from the default " + "is almost certainly not what you want.\n#Access to the " + "external tasks API should be restricted as desired by " + "the\n#tasks_api_access policy. This may change in the " + "future.\n#", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/tasks/{task_id}"}], + ), + base.APIRule( + name="get_tasks", + check_str=("rule:default"), + description="List tasks for all images.\n#\n#This granular policy " + "controls access to tasks, both from the tasks API as " + "well\n#as internal locations in Glance that use tasks (" + "like import). Practically this\n#cannot be more " + "restrictive than the policy that controls import or " + "things will\n#break, and changing it from the default " + "is almost certainly not what you want.\n#Access to the " + "external tasks API should be restricted as desired by " + "the\n#tasks_api_access policy. This may change in the " + "future.\n#", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/tasks"}], + ), + base.APIRule( + name="add_task", + check_str=("rule:default"), + description="List tasks for all images.\n#\n#This granular policy " + "controls access to tasks, both from the tasks API as " + "well\n#as internal locations in Glance that use tasks (" + "like import). Practically this\n#cannot be more " + "restrictive than the policy that controls import or " + "things will\n#break, and changing it from the default " + "is almost certainly not what you want.\n#Access to the " + "external tasks API should be restricted as desired by " + "the\n#tasks_api_access policy. This may change in the " + "future.\n#", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v2/tasks"}], + ), + base.APIRule( + name="modify_task", + check_str=("rule:default"), + description="This policy is not used.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/v2/tasks/{task_id}"}], + ), + base.APIRule( + name="tasks_api_access", + check_str=("role:admin"), + description="\n#This is a generic blanket policy for protecting all " + "task APIs. It is not\n#granular and will not allow you " + "to separate writable and readable task\n#operations " + "into different roles.\n#", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v2/tasks/{task_id}"}, + {"method": "GET", "path": "/v2/tasks"}, + {"method": "POST", "path": "/v2/tasks"}, + {"method": "DELETE", "path": "/v2/tasks/{task_id}"}, + ], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/heat.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/heat.py new file mode 100644 index 0000000..129e503 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/heat.py @@ -0,0 +1,911 @@ +from . import base + +list_rules = ( + base.Rule( + name="context_is_admin", + check_str=("(role:admin and is_admin_project:True) OR (role:admin and system_scope:all)"), + description="Decides what is required for the 'is_admin:True' check to succeed.", + ), + base.Rule( + name="project_admin", + check_str=("role:admin"), + description="Default rule for project admin.", + ), + base.Rule( + name="deny_stack_user", + check_str=("not role:heat_stack_user"), + description="Default rule for deny stack user.", + ), + base.Rule( + name="deny_everybody", + check_str=("!"), + description="Default rule for deny everybody.", + ), + base.Rule( + name="allow_everybody", + check_str=(""), + description="Default rule for allow everybody.", + ), + base.Rule( + name="cloudformation:ListStacks", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:CreateStack", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:DescribeStacks", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:DeleteStack", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:UpdateStack", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:CancelUpdateStack", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:DescribeStackEvents", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:ValidateTemplate", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:GetTemplate", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:EstimateTemplateCost", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:DescribeStackResource", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and project_id:%(project_id)s) " + "or (role:heat_stack_user and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:DescribeStackResources", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="cloudformation:ListStackResources", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="No description", + ), + base.Rule( + name="resource_types:OS::Nova::Flavor", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Cinder::EncryptedVolumeType", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Cinder::VolumeType", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Cinder::Quota", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Neutron::Quota", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Nova::Quota", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Octavia::Quota", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Manila::ShareType", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Neutron::ProviderNet", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Neutron::QoSPolicy", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Neutron::QoSBandwidthLimitRule", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Neutron::QoSDscpMarkingRule", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Neutron::QoSMinimumBandwidthRule", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Neutron::Segment", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Nova::HostAggregate", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Cinder::QoSSpecs", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Cinder::QoSAssociation", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Keystone::*", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Blazar::Host", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Octavia::Flavor", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="resource_types:OS::Octavia::FlavorProfile", + check_str=("rule:project_admin"), + description="No description", + ), + base.Rule( + name="service:index", + check_str=("role:reader and system_scope:all"), + description="No description", + ), + base.APIRule( + name="actions:action", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Performs non-lifecycle operations on the stack " + "(Snapshot, Resume, Cancel update, or check stack " + "resources). This is the default for all actions but " + "can be overridden by more specific policies " + "for individual actions.", + scope_types=["project"], + operations=[ + {"method": "POST", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/actions"}, + ], + ), + base.APIRule( + name="actions:snapshot", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create stack snapshot", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/actions"}, + ], + ), + base.APIRule( + name="actions:suspend", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Suspend a stack.", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/actions"}, + ], + ), + base.APIRule( + name="actions:resume", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Resume a suspended stack.", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/actions", + }, + ], + ), + base.APIRule( + name="actions:check", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="Check stack resources.", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/actions", + }, + ], + ), + base.APIRule( + name="actions:cancel_update", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Cancel stack operation and roll back.", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/actions", + }, + ], + ), + base.APIRule( + name="actions:cancel_without_rollback", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Cancel stack operation without rolling back.", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/actions", + }, + ], + ), + base.APIRule( + name="build_info:build_info", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="Show build information.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/build_info"}], + ), + base.APIRule( + name="events:index", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="List events.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/events"}, + ], + ), + base.APIRule( + name="events:show", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Show event.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{" + "stack_id}/resources/{resource_name}/events/{" + "event_id}", + }, + ], + ), + base.APIRule( + name="resource:index", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="List resources.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/resources", + }, + ], + ), + base.APIRule( + name="resource:metadata", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or (role:heat_stack_user and " + "project_id:%(project_id)s) " + ), + description="Show resource metadata.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{" + "stack_id}/resources/{resource_name}/metadata", + }, + ], + ), + base.APIRule( + name="resource:signal", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or (role:heat_stack_user and " + "project_id:%(project_id)s) " + ), + description="Signal resource.", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{" + "stack_id}/resources/{resource_name}/signal", + }, + ], + ), + base.APIRule( + name="resource:mark_unhealthy", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Mark resource as unhealthy.", + scope_types=["system", "project"], + operations=[ + { + "method": "PATCH", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{" + "stack_id}/resources/{resource_name_or_physical_id}", + }, + ], + ), + base.APIRule( + name="resource:show", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Show resource.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}" + "/{stack_id}/resources/{resource_name}", + }, + ], + ), + base.APIRule( + name="software_configs:global_index", + check_str=("role:reader and system_scope:all"), + description="List configs globally.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/software_configs"}], + ), + base.APIRule( + name="software_configs:index", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="List configs.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/software_configs"}], + ), + base.APIRule( + name="software_configs:create", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="Create config.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v1/{tenant_id}/software_configs"}], + ), + base.APIRule( + name="software_configs:show", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="Show config details.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/software_configs/{config_id}"}], + ), + base.APIRule( + name="software_configs:delete", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete config.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/v1/{tenant_id}/software_configs/{config_id}"}], + ), + base.APIRule( + name="software_deployments:index", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List deployments.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/software_deployments"}], + ), + base.APIRule( + name="software_deployments:create", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create deployment.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v1/{tenant_id}/software_deployments"}], + ), + base.APIRule( + name="software_deployments:show", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Show deployment details.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v1/{tenant_id}/software_deployments/{deployment_id}"}, + ], + ), + base.APIRule( + name="software_deployments:update", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update deployment.", + scope_types=["system", "project"], + operations=[ + {"method": "PUT", "path": "/v1/{tenant_id}/software_deployments/{deployment_id}"}, + ], + ), + base.APIRule( + name="software_deployments:delete", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete deployment.", + scope_types=["system", "project"], + operations=[ + {"method": "DELETE", "path": "/v1/{tenant_id}/software_deployments/{deployment_id}"}, + ], + ), + base.APIRule( + name="software_deployments:metadata", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and project_id:%(project_id)s) " + "or (role:heat_stack_user and project_id:%(project_id)s)" + ), + description="Show server configuration metadata.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/software_deployments/metadata/{server_id}", + }, + ], + ), + base.APIRule( + name="stacks:abandon", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Abandon stack.", + scope_types=["system", "project"], + operations=[ + { + "method": "DELETE", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/abandon", + }, + ], + ), + base.APIRule( + name="stacks:create", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create stack.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v1/{tenant_id}/stacks"}], + ), + base.APIRule( + name="stacks:delete", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete stack.", + scope_types=["system", "project"], + operations=[ + {"method": "DELETE", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}"}, + ], + ), + base.APIRule( + name="stacks:detail", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List stacks in detail.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/stacks"}], + ), + base.APIRule( + name="stacks:export", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Export stack.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/export"}, + ], + ), + base.APIRule( + name="stacks:generate_template", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Generate stack template.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/template", + }, + ], + ), + base.APIRule( + name="stacks:global_index", + check_str=("role:reader and system_scope:all"), + description="List stacks globally.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/stacks"}], + ), + base.APIRule( + name="stacks:index", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List stacks.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/stacks"}], + ), + base.APIRule( + name="stacks:list_resource_types", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List resource types.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/resource_types"}], + ), + base.APIRule( + name="stacks:list_template_versions", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List template versions.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/template_versions"}], + ), + base.APIRule( + name="stacks:list_template_functions", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List template functions.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/template_versions/{template_version}/functions", + }, + ], + ), + base.APIRule( + name="stacks:lookup", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and project_id:%(project_id)s) " + "or (role:heat_stack_user and project_id:%(project_id)s)" + ), + description="Find stack.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/stacks/{stack_identity}"}], + ), + base.APIRule( + name="stacks:preview", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Preview stack.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v1/{tenant_id}/stacks/preview"}], + ), + base.APIRule( + name="stacks:resource_schema", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Show resource type schema.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/resource_types/{type_name}"}], + ), + base.APIRule( + name="stacks:show", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Show stack.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v1/{tenant_id}/stacks/{stack_identity}"}], + ), + base.APIRule( + name="stacks:template", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get stack template.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/template", + }, + ], + ), + base.APIRule( + name="stacks:environment", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get stack environment.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/environment", + }, + ], + ), + base.APIRule( + name="stacks:files", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get stack files.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/files"}, + ], + ), + base.APIRule( + name="stacks:update", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update stack.", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}"}], + ), + base.APIRule( + name="stacks:update_patch", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update stack (PATCH).", + scope_types=["system", "project"], + operations=[ + {"method": "PATCH", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}"}, + ], + ), + base.APIRule( + name="stacks:update_no_change", + check_str=("rule:stacks:update_patch"), + description="Update stack (PATCH) with no changes.", + scope_types=["system", "project"], + operations=[ + {"method": "PATCH", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}"}, + ], + ), + base.APIRule( + name="stacks:preview_update", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Preview update stack.", + scope_types=["system", "project"], + operations=[ + {"method": "PUT", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/preview"}, + ], + ), + base.APIRule( + name="stacks:preview_update_patch", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Preview update stack (PATCH).", + scope_types=["system", "project"], + operations=[ + {"method": "PATCH", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/preview"}, + ], + ), + base.APIRule( + name="stacks:validate_template", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Validate template.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v1/{tenant_id}/validate"}], + ), + base.APIRule( + name="stacks:snapshot", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Snapshot Stack.", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/snapshots", + }, + ], + ), + base.APIRule( + name="stacks:show_snapshot", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Show snapshot.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/" + "{stack_id}/snapshots/{snapshot_id}", + }, + ], + ), + base.APIRule( + name="stacks:delete_snapshot", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete snapshot.", + scope_types=["system", "project"], + operations=[ + { + "method": "DELETE", + "path": "/v1/{tenant_id}/stacks/{stack_name}" + "/{stack_id}/snapshots/{snapshot_id}", + }, + ], + ), + base.APIRule( + name="stacks:list_snapshots", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List snapshots.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/snapshots", + }, + ], + ), + base.APIRule( + name="stacks:restore_snapshot", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Restore snapshot.", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/v1/{tenant_id}/stacks/{stack_name}/" + "{stack_id}/snapshots/{snapshot_id}/restore", + }, + ], + ), + base.APIRule( + name="stacks:list_outputs", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="List outputs.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/outputs"}, + ], + ), + base.APIRule( + name="stacks:show_output", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="Show outputs.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/outputs/{output_key}", + }, + ], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/ironic.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/ironic.py new file mode 100644 index 0000000..7678953 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/ironic.py @@ -0,0 +1,1096 @@ +from . import base + +list_rules = ( + base.Rule( + name="admin_api", + check_str=("role:admin or role:administrator"), + description="Legacy rule for cloud admin access", + ), + base.Rule( + name="public_api", + check_str=("is_public_api:True"), + description="Internal flag for public API routes", + ), + base.Rule( + name="show_password", + check_str=("!"), + description="Show or mask secrets within node driver information in API responses", + ), + base.Rule( + name="show_instance_secrets", + check_str=("!"), + description="Show or mask secrets within instance information in API responses", + ), + base.Rule( + name="is_member", + check_str=( + "(project_domain_id:default or project_domain_id:None) and " + "(project_name:demo or project_name:baremetal)" + ), + description="May be used to restrict access to specific projects", + ), + base.Rule( + name="is_observer", + check_str=("rule:is_member and (role:observer or role:baremetal_observer)"), + description="Read-only API access", + ), + base.Rule( + name="is_admin", + check_str=("rule:admin_api or (rule:is_member and role:baremetal_admin)"), + description="Full read/write API access", + ), + base.Rule( + name="is_node_owner", + check_str=("project_id:%(node.owner)s"), + description="Owner of node", + ), + base.Rule( + name="is_node_lessee", + check_str=("project_id:%(node.lessee)s"), + description="Lessee of node", + ), + base.Rule( + name="is_allocation_owner", + check_str=("project_id:%(allocation.owner)s"), + description="Owner of allocation", + ), + base.APIRule( + name="baremetal:node:create", + check_str=("role:admin and system_scope:all"), + description="Create Node records", + scope_types=["system"], + operations=[{"method": "POST", "path": "/nodes"}], + ), + base.APIRule( + name="baremetal:node:list", + check_str=("role:reader"), + description="Retrieve multiple Node records, " + "filtered by an explicit owner or the client project_id", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/nodes"}, + {"method": "GET", "path": "/nodes/detail"}, + ], + ), + base.APIRule( + name="baremetal:node:list_all", + check_str=("role:reader and system_scope:all"), + description="Retrieve multiple Node records", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/nodes"}, + {"method": "GET", "path": "/nodes/detail"}, + ], + ), + base.APIRule( + name="baremetal:node:get", + check_str=( + "(role:reader and system_scope:all) or " + "(role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Retrieve a single Node record", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:get:filter_threshold", + check_str=("role:reader and system_scope:all"), + description="Filter to allow operators to govern the threshold where " + "information should be filtered. Non-authorized users " + "will be subjected to additional API policy checks for " + "API content response bodies.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:get:last_error", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(node.owner)s) " + ), + description="Governs if the node last_error field is masked from " + "APIclients with insufficent privileges.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:get:reservation", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(node.owner)s) " + ), + description="Governs if the node reservation field is masked from " + "APIclients with insufficent privileges.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:get:driver_internal_info", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(node.owner)s) " + ), + description="Governs if the node driver_internal_info field is " + "masked from API clients with insufficent privileges.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:get:driver_info", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(node.owner)s) " + ), + description="Governs if the driver_info field is masked from " + "APIclients with insufficent privileges.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:driver_info", + check_str=( + "(role:member and system_scope:all) or (role:member and " + "project_id:%(node.owner)s) " + ), + description="Governs if node driver_info field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:properties", + check_str=( + "(role:member and system_scope:all) or (role:member and " + "project_id:%(node.owner)s) " + ), + description="Governs if node properties field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:chassis_uuid", + check_str=("role:admin and system_scope:all"), + description="Governs if node chassis_uuid field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:instance_uuid", + check_str=( + "(role:member and system_scope:all) or (role:member and " + "project_id:%(node.owner)s) " + ), + description="Governs if node instance_uuid field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:lessee", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Governs if node lessee field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:owner", + check_str=("role:member and system_scope:all"), + description="Governs if node owner field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:driver_interfaces", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Governs if node driver and driver interfaces field " + "can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:network_data", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Governs if node driver_info field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:conductor_group", + check_str=("role:member and system_scope:all"), + description="Governs if node conductor_group field can be updated " + "via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:name", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Governs if node name field can be updated via the API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update:retired", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Governs if node retired and retired reason can be " + "updated by API clients.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Generalized update of node records", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update_extra", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Update Node extra field", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update_instance_info", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Update Node instance_info field", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:update_owner_provisioned", + check_str=("role:admin and system_scope:all"), + description="Update Node owner even when Node is provisioned", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:delete", + check_str=("role:admin and system_scope:all"), + description="Delete Node records", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:node:validate", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Request active validation of Nodes", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}/validate"}], + ), + base.APIRule( + name="baremetal:node:set_maintenance", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Set maintenance flag, taking a Node out of service", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/nodes/{node_ident}/maintenance"}], + ), + base.APIRule( + name="baremetal:node:clear_maintenance", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Clear maintenance flag, placing the Node into service again", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/nodes/{node_ident}/maintenance"}], + ), + base.APIRule( + name="baremetal:node:get_boot_device", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Retrieve Node boot device metadata", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/nodes/{node_ident}/management/boot_device"}, + {"method": "GET", "path": "/nodes/{node_ident}/management/boot_device/supported"}, + ], + ), + base.APIRule( + name="baremetal:node:set_boot_device", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Change Node boot device", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/nodes/{node_ident}/management/boot_device"}], + ), + base.APIRule( + name="baremetal:node:get_indicator_state", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Retrieve Node indicators and their states", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/nodes/{node_ident}/management/indicators/{component}/{indicator}", + }, + {"method": "GET", "path": "/nodes/{node_ident}/management/indicators"}, + ], + ), + base.APIRule( + name="baremetal:node:set_indicator_state", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Change Node indicator state", + scope_types=["system", "project"], + operations=[ + { + "method": "PUT", + "path": "/nodes/{node_ident}/management/indicators/{component}/{indicator}", + }, + ], + ), + base.APIRule( + name="baremetal:node:inject_nmi", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Inject NMI for a node", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/nodes/{node_ident}/management/inject_nmi"}], + ), + base.APIRule( + name="baremetal:node:get_states", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="View Node power and provision state", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}/states"}], + ), + base.APIRule( + name="baremetal:node:set_power_state", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Change Node power status", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/nodes/{node_ident}/states/power"}], + ), + base.APIRule( + name="baremetal:node:set_provision_state", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Change Node provision status", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/nodes/{node_ident}/states/provision"}], + ), + base.APIRule( + name="baremetal:node:set_raid_state", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Change Node RAID status", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/nodes/{node_ident}/states/raid"}], + ), + base.APIRule( + name="baremetal:node:get_console", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Get Node console connection information", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}/states/console"}], + ), + base.APIRule( + name="baremetal:node:set_console_state", + check_str=( + "(role:member and system_scope:all) or (role:member and project_id:%(node.owner)s)" + ), + description="Change Node console status", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/nodes/{node_ident}/states/console"}], + ), + base.APIRule( + name="baremetal:node:vif:list", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="List VIFs attached to node", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}/vifs"}], + ), + base.APIRule( + name="baremetal:node:vif:attach", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Attach a VIF to a node", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/nodes/{node_ident}/vifs"}], + ), + base.APIRule( + name="baremetal:node:vif:detach", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Detach a VIF from a node", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/nodes/{node_ident}/vifs/{node_vif_ident}"}], + ), + base.APIRule( + name="baremetal:node:traits:list", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="List node traits", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/nodes/{node_ident}/traits"}], + ), + base.APIRule( + name="baremetal:node:traits:set", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Add a trait to, or replace all traits of, a node", + scope_types=["system", "project"], + operations=[ + {"method": "PUT", "path": "/nodes/{node_ident}/traits"}, + {"method": "PUT", "path": "/nodes/{node_ident}/traits/{trait}"}, + ], + ), + base.APIRule( + name="baremetal:node:traits:delete", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Remove one or all traits from a node", + scope_types=["system", "project"], + operations=[ + {"method": "DELETE", "path": "/nodes/{node_ident}/traits"}, + {"method": "DELETE", "path": "/nodes/{node_ident}/traits/{trait}"}, + ], + ), + base.APIRule( + name="baremetal:node:bios:get", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Retrieve Node BIOS information", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/nodes/{node_ident}/bios"}, + {"method": "GET", "path": "/nodes/{node_ident}/bios/{setting}"}, + ], + ), + base.APIRule( + name="baremetal:node:disable_cleaning", + check_str=("role:admin and system_scope:all"), + description="Disable Node disk cleaning", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/nodes/{node_ident}"}], + ), + base.APIRule( + name="baremetal:port:get", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Retrieve Port records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/ports/{port_id}"}, + {"method": "GET", "path": "/nodes/{node_ident}/ports"}, + {"method": "GET", "path": "/nodes/{node_ident}/ports/detail"}, + {"method": "GET", "path": "/portgroups/{portgroup_ident}/ports"}, + {"method": "GET", "path": "/portgroups/{portgroup_ident}/ports/detail"}, + ], + ), + base.APIRule( + name="baremetal:port:list", + check_str=("role:reader"), + description="Retrieve multiple Port records, filtered by owner", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/detail"}, + ], + ), + base.APIRule( + name="baremetal:port:list_all", + check_str=("role:reader and system_scope:all"), + description="Retrieve multiple Port records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/detail"}, + ], + ), + base.APIRule( + name="baremetal:port:create", + check_str=( + "(role:admin and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Create Port records", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="baremetal:port:delete", + check_str=( + "(role:admin and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Delete Port records", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/ports/{port_id}"}], + ), + base.APIRule( + name="baremetal:port:update", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Update Port records", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/ports/{port_id}"}], + ), + base.APIRule( + name="baremetal:portgroup:get", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Retrieve Portgroup records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/portgroups"}, + {"method": "GET", "path": "/portgroups/detail"}, + {"method": "GET", "path": "/portgroups/{portgroup_ident}"}, + {"method": "GET", "path": "/nodes/{node_ident}/portgroups"}, + {"method": "GET", "path": "/nodes/{node_ident}/portgroups/detail"}, + ], + ), + base.APIRule( + name="baremetal:portgroup:create", + check_str=( + "(role:admin and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Create Portgroup records", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/portgroups"}], + ), + base.APIRule( + name="baremetal:portgroup:delete", + check_str=( + "(role:admin and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Delete Portgroup records", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/portgroups/{portgroup_ident}"}], + ), + base.APIRule( + name="baremetal:portgroup:update", + check_str=( + "(role:member and system_scope:all) or (role:admin and project_id:%(node.owner)s)" + ), + description="Update Portgroup records", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/portgroups/{portgroup_ident}"}], + ), + base.APIRule( + name="baremetal:portgroup:list", + check_str=("role:reader"), + description="Retrieve multiple Port records, filtered by owner", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/portgroups"}, + {"method": "GET", "path": "/portgroups/detail"}, + ], + ), + base.APIRule( + name="baremetal:portgroup:list_all", + check_str=("role:reader and system_scope:all"), + description="Retrieve multiple Port records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/portgroups"}, + {"method": "GET", "path": "/portgroups/detail"}, + ], + ), + base.APIRule( + name="baremetal:chassis:get", + check_str=("role:reader and system_scope:all"), + description="Retrieve Chassis records", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/chassis"}, + {"method": "GET", "path": "/chassis/detail"}, + {"method": "GET", "path": "/chassis/{chassis_id}"}, + ], + ), + base.APIRule( + name="baremetal:chassis:create", + check_str=("role:admin and system_scope:all"), + description="Create Chassis records", + scope_types=["system"], + operations=[{"method": "POST", "path": "/chassis"}], + ), + base.APIRule( + name="baremetal:chassis:delete", + check_str=("role:admin and system_scope:all"), + description="Delete Chassis records", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/chassis/{chassis_id}"}], + ), + base.APIRule( + name="baremetal:chassis:update", + check_str=("role:member and system_scope:all"), + description="Update Chassis records", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/chassis/{chassis_id}"}], + ), + base.APIRule( + name="baremetal:driver:get", + check_str=("role:reader and system_scope:all"), + description="View list of available drivers", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/drivers"}, + {"method": "GET", "path": "/drivers/{driver_name}"}, + ], + ), + base.APIRule( + name="baremetal:driver:get_properties", + check_str=("role:reader and system_scope:all"), + description="View driver-specific properties", + scope_types=["system"], + operations=[{"method": "GET", "path": "/drivers/{driver_name}/properties"}], + ), + base.APIRule( + name="baremetal:driver:get_raid_logical_disk_properties", + check_str=("role:reader and system_scope:all"), + description="View driver-specific RAID metadata", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/drivers/{driver_name}/raid/logical_disk_properties"}, + ], + ), + base.APIRule( + name="baremetal:node:vendor_passthru", + check_str=("role:admin and system_scope:all"), + description="Access vendor-specific Node functions", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "nodes/{node_ident}/vendor_passthru/methods"}, + {"method": "GET", "path": "nodes/{node_ident}/vendor_passthru?method={method_name}"}, + {"method": "PUT", "path": "nodes/{node_ident}/vendor_passthru?method={method_name}"}, + {"method": "POST", "path": "nodes/{node_ident}/vendor_passthru?method={method_name}"}, + { + "method": "PATCH", + "path": "nodes/{node_ident}/vendor_passthru?method={method_name}", + }, + { + "method": "DELETE", + "path": "nodes/{node_ident}/vendor_passthru?method={method_name}", + }, + ], + ), + base.APIRule( + name="baremetal:driver:vendor_passthru", + check_str=("role:admin and system_scope:all"), + description="Access vendor-specific Driver functions", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "drivers/{driver_name}/vendor_passthru/methods"}, + { + "method": "GET", + "path": "drivers/{driver_name}/vendor_passthru?method={method_name}", + }, + { + "method": "PUT", + "path": "drivers/{driver_name}/vendor_passthru?method={method_name}", + }, + { + "method": "POST", + "path": "drivers/{driver_name}/vendor_passthru?method={method_name}", + }, + { + "method": "PATCH", + "path": "drivers/{driver_name}/vendor_passthru?method={method_name}", + }, + { + "method": "DELETE", + "path": "drivers/{driver_name}/vendor_passthru?method={method_name}", + }, + ], + ), + base.APIRule( + name="baremetal:node:ipa_heartbeat", + check_str=(""), + description="Receive heartbeats from IPA ramdisk", + scope_types=["project"], + operations=[{"method": "POST", "path": "/heartbeat/{node_ident}"}], + ), + base.APIRule( + name="baremetal:driver:ipa_lookup", + check_str=(""), + description="Access IPA ramdisk functions", + scope_types=["project"], + operations=[{"method": "GET", "path": "/lookup"}], + ), + base.APIRule( + name="baremetal:volume:list_all", + check_str=("role:reader and system_scope:all"), + description="Retrieve a list of all Volume connector and target records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/volume/connectors"}, + {"method": "GET", "path": "/volume/targets"}, + {"method": "GET", "path": "/nodes/{node_ident}/volume/connectors"}, + {"method": "GET", "path": "/nodes/{node_ident}/volume/targets"}, + ], + ), + base.APIRule( + name="baremetal:volume:list", + check_str=("role:reader"), + description="Retrieve a list of Volume connector and target records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/volume/connectors"}, + {"method": "GET", "path": "/volume/targets"}, + {"method": "GET", "path": "/nodes/{node_ident}/volume/connectors"}, + {"method": "GET", "path": "/nodes/{node_ident}/volume/targets"}, + ], + ), + base.APIRule( + name="baremetal:volume:get", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and (project_id:%(node.owner)s " + "or project_id:%(node.lessee)s))" + ), + description="Retrieve Volume connector and target records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/volume"}, + {"method": "GET", "path": "/volume/connectors"}, + {"method": "GET", "path": "/volume/connectors/{volume_connector_id}"}, + {"method": "GET", "path": "/volume/targets"}, + {"method": "GET", "path": "/volume/targets/{volume_target_id}"}, + {"method": "GET", "path": "/nodes/{node_ident}/volume"}, + {"method": "GET", "path": "/nodes/{node_ident}/volume/connectors"}, + {"method": "GET", "path": "/nodes/{node_ident}/volume/targets"}, + ], + ), + base.APIRule( + name="baremetal:volume:create", + check_str=( + "(role:member and system_scope:all) " + "or (role:admin and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Create Volume connector and target records", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/volume/connectors"}, + {"method": "POST", "path": "/volume/targets"}, + ], + ), + base.APIRule( + name="baremetal:volume:delete", + check_str=( + "(role:member and system_scope:all) " + "or (role:admin and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Delete Volume connector and target records", + scope_types=["system", "project"], + operations=[ + {"method": "DELETE", "path": "/volume/connectors/{volume_connector_id}"}, + {"method": "DELETE", "path": "/volume/targets/{volume_target_id}"}, + ], + ), + base.APIRule( + name="baremetal:volume:update", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(node.owner)s) " + "or (role:admin and project_id:%(node.lessee)s)" + ), + description="Update Volume connector and target records", + scope_types=["system", "project"], + operations=[ + {"method": "PATCH", "path": "/volume/connectors/{volume_connector_id}"}, + {"method": "PATCH", "path": "/volume/targets/{volume_target_id}"}, + ], + ), + base.APIRule( + name="baremetal:volume:view_target_properties", + check_str=("(role:reader and system_scope:all) or (role:admin)"), + description="Ability to view volume target properties", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/volume/connectors/{volume_connector_id}"}, + {"method": "GET", "path": "/volume/targets/{volume_target_id}"}, + ], + ), + base.APIRule( + name="baremetal:conductor:get", + check_str=("role:reader and system_scope:all"), + description="Retrieve Conductor records", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/conductors"}, + {"method": "GET", "path": "/conductors/{hostname}"}, + ], + ), + base.APIRule( + name="baremetal:allocation:get", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and project_id:%(allocation.owner)s)" + ), + description="Retrieve Allocation records", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/allocations/{allocation_id}"}, + {"method": "GET", "path": "/nodes/{node_ident}/allocation"}, + ], + ), + base.APIRule( + name="baremetal:allocation:list", + check_str=("role:reader"), + description="Retrieve multiple Allocation records, filtered by owner", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/allocations"}], + ), + base.APIRule( + name="baremetal:allocation:list_all", + check_str=("role:reader and system_scope:all"), + description="Retrieve multiple Allocation records", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/allocations"}], + ), + base.APIRule( + name="baremetal:allocation:create", + check_str=("(role:member and system_scope:all) or (role:member)"), + description="Create Allocation records", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/allocations"}], + ), + base.APIRule( + name="baremetal:allocation:create_restricted", + check_str=("role:member and system_scope:all"), + description="Create Allocation records with a specific owner.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/allocations"}], + ), + base.APIRule( + name="baremetal:allocation:delete", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(allocation.owner)s)" + ), + description="Delete Allocation records", + scope_types=["system", "project"], + operations=[ + {"method": "DELETE", "path": "/allocations/{allocation_id}"}, + {"method": "DELETE", "path": "/nodes/{node_ident}/allocation"}, + ], + ), + base.APIRule( + name="baremetal:allocation:update", + check_str=( + "(role:member and system_scope:all) " + "or (role:member and project_id:%(allocation.owner)s)" + ), + description="Change name and extra fields of an allocation", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/allocations/{allocation_id}"}], + ), + base.APIRule( + name="baremetal:allocation:create_pre_rbac", + check_str=( + "(rule:is_member and role:baremetal_admin) " + "or (is_admin_project:True and role:admin)" + ), + description="Logical restrictor to prevent legacy allocation rule " + "missuse - Requires blank allocations to originate from " + "the legacy baremetal_admin.", + scope_types=["project"], + operations=[{"method": "PATCH", "path": "/allocations/{allocation_id}"}], + ), + base.APIRule( + name="baremetal:events:post", + check_str=("role:admin and system_scope:all"), + description="Post events", + scope_types=["system"], + operations=[{"method": "POST", "path": "/events"}], + ), + base.APIRule( + name="baremetal:deploy_template:get", + check_str=("role:reader and system_scope:all"), + description="Retrieve Deploy Template records", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/deploy_templates"}, + {"method": "GET", "path": "/deploy_templates/{deploy_template_ident}"}, + ], + ), + base.APIRule( + name="baremetal:deploy_template:create", + check_str=("role:admin and system_scope:all"), + description="Create Deploy Template records", + scope_types=["system"], + operations=[{"method": "POST", "path": "/deploy_templates"}], + ), + base.APIRule( + name="baremetal:deploy_template:delete", + check_str=("role:admin and system_scope:all"), + description="Delete Deploy Template records", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/deploy_templates/{deploy_template_ident}"}], + ), + base.APIRule( + name="baremetal:deploy_template:update", + check_str=("role:admin and system_scope:all"), + description="Update Deploy Template records", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/deploy_templates/{deploy_template_ident}"}], + ), + base.APIRule( + name="introspection", + check_str=("rule:public_api"), + description="Access the API root for available versions information", + scope_types=["project"], + operations=[{"method": "GET", "path": "/"}], + ), + base.APIRule( + name="introspection:version", + check_str=("rule:public_api"), + description="Access the versioned API root for version information", + scope_types=["project"], + operations=[{"method": "GET", "path": "/{version}"}], + ), + base.APIRule( + name="introspection:continue", + check_str=("rule:public_api"), + description="Ramdisk callback to continue introspection", + scope_types=["project"], + operations=[{"method": "POST", "path": "/continue"}], + ), + base.APIRule( + name="introspection:status", + check_str=("role:reader and system_scope:all"), + description="Get introspection status", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/introspection"}, + {"method": "GET", "path": "/introspection/{node_id}"}, + ], + ), + base.APIRule( + name="introspection:start", + check_str=("role:admin and system_scope:all"), + description="Start introspection", + scope_types=["project"], + operations=[{"method": "POST", "path": "/introspection/{node_id}"}], + ), + base.APIRule( + name="introspection:abort", + check_str=("role:admin and system_scope:all"), + description="Abort introspection", + scope_types=["project"], + operations=[{"method": "POST", "path": "/introspection/{node_id}/abort"}], + ), + base.APIRule( + name="introspection:data", + check_str=("role:admin and system_scope:all"), + description="Get introspection data", + scope_types=["project"], + operations=[{"method": "GET", "path": "/introspection/{node_id}/data"}], + ), + base.APIRule( + name="introspection:reapply", + check_str=("role:admin and system_scope:all"), + description="Reapply introspection on stored data", + scope_types=["project"], + operations=[{"method": "POST", "path": "/introspection/{node_id}/data/unprocessed"}], + ), + base.APIRule( + name="introspection:rule:get", + check_str=("role:admin and system_scope:all"), + description="Get introspection rule(s)", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/rules"}, + {"method": "GET", "path": "/rules/{rule_id}"}, + ], + ), + base.APIRule( + name="introspection:rule:delete", + check_str=("role:admin and system_scope:all"), + description="Delete introspection rule(s)", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/rules"}, + {"method": "DELETE", "path": "/rules/{rule_id}"}, + ], + ), + base.APIRule( + name="introspection:rule:create", + check_str=("role:admin and system_scope:all"), + description="Create introspection rule", + scope_types=["project"], + operations=[{"method": "POST", "path": "/rules"}], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/keystone.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/keystone.py new file mode 100644 index 0000000..656e09e --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/keystone.py @@ -0,0 +1,2193 @@ +from . import base + +list_rules = ( + base.Rule( + name="admin_required", + check_str=("role:admin or is_admin:1"), + description="No description", + ), + base.Rule( + name="service_role", + check_str=("role:service"), + description="No description", + ), + base.Rule( + name="service_or_admin", + check_str=("rule:admin_required or rule:service_role"), + description="No description", + ), + base.Rule( + name="owner", + check_str=("user_id:%(user_id)s"), + description="No description", + ), + base.Rule( + name="admin_or_owner", + check_str=("rule:admin_required or rule:owner"), + description="No description", + ), + base.Rule( + name="token_subject", + check_str=("user_id:%(target.token.user_id)s"), + description="No description", + ), + base.Rule( + name="admin_or_token_subject", + check_str=("rule:admin_required or rule:token_subject"), + description="No description", + ), + base.Rule( + name="service_admin_or_token_subject", + check_str=("rule:service_or_admin or rule:token_subject"), + description="No description", + ), + base.APIRule( + name="identity:get_access_rule", + check_str=("(role:reader and system_scope:all) or user_id:%(target.user.id)s"), + description="Show access rule details.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/users/{user_id}/access_rules/{access_rule_id}"}, + {"method": "HEAD", "path": "/v3/users/{user_id}/access_rules/{access_rule_id}"}, + ], + ), + base.APIRule( + name="identity:list_access_rules", + check_str=("(role:reader and system_scope:all) or user_id:%(target.user.id)s"), + description="List access rules for a user.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/users/{user_id}/access_rules"}, + {"method": "HEAD", "path": "/v3/users/{user_id}/access_rules"}, + ], + ), + base.APIRule( + name="identity:delete_access_rule", + check_str=("(role:admin and system_scope:all) or user_id:%(target.user.id)s"), + description="Delete an access_rule.", + scope_types=["system", "project"], + operations=[ + {"method": "DELETE", "path": "/v3/users/{user_id}/access_rules/{access_rule_id}"}, + ], + ), + base.APIRule( + name="identity:authorize_request_token", + check_str=("rule:admin_required"), + description="Authorize OAUTH1 request token.", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v3/OS-OAUTH1/authorize/{request_token_id}"}], + ), + base.APIRule( + name="identity:get_access_token", + check_str=("rule:admin_required"), + description="Get OAUTH1 access token for user by access token ID.", + scope_types=["project"], + operations=[ + { + "method": "GET", + "path": "/v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}", + }, + ], + ), + base.APIRule( + name="identity:get_access_token_role", + check_str=("rule:admin_required"), + description="Get role for user OAUTH1 access token.", + scope_types=["project"], + operations=[ + { + "method": "GET", + "path": "/v3/users/{user_id}/OS-OAUTH1/" + "access_tokens/{access_token_id}/roles/{role_id}", + }, + ], + ), + base.APIRule( + name="identity:list_access_tokens", + check_str=("rule:admin_required"), + description="List OAUTH1 access tokens for user.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v3/users/{user_id}/OS-OAUTH1/access_tokens"}], + ), + base.APIRule( + name="identity:list_access_token_roles", + check_str=("rule:admin_required"), + description="List OAUTH1 access token roles.", + scope_types=["project"], + operations=[ + { + "method": "GET", + "path": "/v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles", + }, + ], + ), + base.APIRule( + name="identity:delete_access_token", + check_str=("rule:admin_required"), + description="Delete OAUTH1 access token.", + scope_types=["project"], + operations=[ + { + "method": "DELETE", + "path": "/v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}", + }, + ], + ), + base.APIRule( + name="identity:get_application_credential", + check_str=("(role:reader and system_scope:all) or rule:owner"), + description="Show application credential details.", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/v3/users/{user_id}/application_credentials/" + "{application_credential_id}", + }, + { + "method": "HEAD", + "path": "/v3/users/{user_id}/application_credentials/" + "{application_credential_id}", + }, + ], + ), + base.APIRule( + name="identity:list_application_credentials", + check_str=("(role:reader and system_scope:all) or rule:owner"), + description="List application credentials for a user.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/users/{user_id}/application_credentials"}, + {"method": "HEAD", "path": "/v3/users/{user_id}/application_credentials"}, + ], + ), + base.APIRule( + name="identity:create_application_credential", + check_str=("user_id:%(user_id)s"), + description="Create an application credential.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v3/users/{user_id}/application_credentials"}], + ), + base.APIRule( + name="identity:delete_application_credential", + check_str=("(role:admin and system_scope:all) or rule:owner"), + description="Delete an application credential.", + scope_types=["system", "project"], + operations=[ + { + "method": "DELETE", + "path": "/v3/users/{user_id}/application_credentials/" + "{application_credential_id}", + }, + ], + ), + base.APIRule( + name="identity:get_auth_catalog", + check_str=(""), + description="Get service catalog.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v3/auth/catalog"}, + {"method": "HEAD", "path": "/v3/auth/catalog"}, + ], + ), + base.APIRule( + name="identity:get_auth_projects", + check_str=(""), + description="List all projects a user has access to via role assignments.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v3/auth/projects"}, + {"method": "HEAD", "path": "/v3/auth/projects"}, + ], + ), + base.APIRule( + name="identity:get_auth_domains", + check_str=(""), + description="List all domains a user has access to via role assignments.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v3/auth/domains"}, + {"method": "HEAD", "path": "/v3/auth/domains"}, + ], + ), + base.APIRule( + name="identity:get_auth_system", + check_str=(""), + description="List systems a user has access to via role assignments.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v3/auth/system"}, + {"method": "HEAD", "path": "/v3/auth/system"}, + ], + ), + base.APIRule( + name="identity:get_consumer", + check_str=("role:reader and system_scope:all"), + description="Show OAUTH1 consumer details.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/OS-OAUTH1/consumers/{consumer_id}"}], + ), + base.APIRule( + name="identity:list_consumers", + check_str=("role:reader and system_scope:all"), + description="List OAUTH1 consumers.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/OS-OAUTH1/consumers"}], + ), + base.APIRule( + name="identity:create_consumer", + check_str=("role:admin and system_scope:all"), + description="Create OAUTH1 consumer.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/OS-OAUTH1/consumers"}], + ), + base.APIRule( + name="identity:update_consumer", + check_str=("role:admin and system_scope:all"), + description="Update OAUTH1 consumer.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/OS-OAUTH1/consumers/{consumer_id}"}], + ), + base.APIRule( + name="identity:delete_consumer", + check_str=("role:admin and system_scope:all"), + description="Delete OAUTH1 consumer.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/OS-OAUTH1/consumers/{consumer_id}"}], + ), + base.APIRule( + name="identity:get_credential", + check_str=("(role:reader and system_scope:all) or user_id:%(target.credential.user_id)s"), + description="Show credentials details.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v3/credentials/{credential_id}"}], + ), + base.APIRule( + name="identity:list_credentials", + check_str=("(role:reader and system_scope:all) or user_id:%(target.credential.user_id)s"), + description="List credentials.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v3/credentials"}], + ), + base.APIRule( + name="identity:create_credential", + check_str=("(role:admin and system_scope:all) or user_id:%(target.credential.user_id)s"), + description="Create credential.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v3/credentials"}], + ), + base.APIRule( + name="identity:update_credential", + check_str=("(role:admin and system_scope:all) or user_id:%(target.credential.user_id)s"), + description="Update credential.", + scope_types=["system", "project"], + operations=[{"method": "PATCH", "path": "/v3/credentials/{credential_id}"}], + ), + base.APIRule( + name="identity:delete_credential", + check_str=("(role:admin and system_scope:all) or user_id:%(target.credential.user_id)s"), + description="Delete credential.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/v3/credentials/{credential_id}"}], + ), + base.APIRule( + name="identity:get_domain", + check_str=( + "(role:reader and system_scope:all) " + "or token.domain.id:%(target.domain.id)s " + "or token.project.domain.id:%(target.domain.id)s" + ), + description="Show domain details.", + scope_types=["system", "domain", "project"], + operations=[{"method": "GET", "path": "/v3/domains/{domain_id}"}], + ), + base.APIRule( + name="identity:list_domains", + check_str=("role:reader and system_scope:all"), + description="List domains.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/domains"}], + ), + base.APIRule( + name="identity:create_domain", + check_str=("role:admin and system_scope:all"), + description="Create domain.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/domains"}], + ), + base.APIRule( + name="identity:update_domain", + check_str=("role:admin and system_scope:all"), + description="Update domain.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/domains/{domain_id}"}], + ), + base.APIRule( + name="identity:delete_domain", + check_str=("role:admin and system_scope:all"), + description="Delete domain.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/domains/{domain_id}"}], + ), + base.APIRule( + name="identity:create_domain_config", + check_str=("role:admin and system_scope:all"), + description="Create domain configuration.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/v3/domains/{domain_id}/config"}], + ), + base.APIRule( + name="identity:get_domain_config", + check_str=("role:reader and system_scope:all"), + description="Get the entire domain configuration " + "for a domain, an option group within a domain, " + "or a specific configuration option within a group " + "for a domain.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/domains/{domain_id}/config"}, + {"method": "HEAD", "path": "/v3/domains/{domain_id}/config"}, + {"method": "GET", "path": "/v3/domains/{domain_id}/config/{group}"}, + {"method": "HEAD", "path": "/v3/domains/{domain_id}/config/{group}"}, + {"method": "GET", "path": "/v3/domains/{domain_id}/config/{group}/{option}"}, + {"method": "HEAD", "path": "/v3/domains/{domain_id}/config/{group}/{option}"}, + ], + ), + base.APIRule( + name="identity:get_security_compliance_domain_config", + check_str=(""), + description="Get security compliance domain configuration " + "for either a domain or a specific option in a domain.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/domains/{domain_id}/config/security_compliance"}, + {"method": "HEAD", "path": "/v3/domains/{domain_id}/config/security_compliance"}, + { + "method": "GET", + "path": "v3/domains/{domain_id}/config/security_compliance/{option}", + }, + { + "method": "HEAD", + "path": "v3/domains/{domain_id}/config/security_compliance/{option}", + }, + ], + ), + base.APIRule( + name="identity:update_domain_config", + check_str=("role:admin and system_scope:all"), + description="Update domain configuration for either a domain, " + "specific group or a specific option in a group.", + scope_types=["system"], + operations=[ + {"method": "PATCH", "path": "/v3/domains/{domain_id}/config"}, + {"method": "PATCH", "path": "/v3/domains/{domain_id}/config/{group}"}, + {"method": "PATCH", "path": "/v3/domains/{domain_id}/config/{group}/{option}"}, + ], + ), + base.APIRule( + name="identity:delete_domain_config", + check_str=("role:admin and system_scope:all"), + description="Delete domain configuration for either a domain, " + "specific group or a specific option in a group.", + scope_types=["system"], + operations=[ + {"method": "DELETE", "path": "/v3/domains/{domain_id}/config"}, + {"method": "DELETE", "path": "/v3/domains/{domain_id}/config/{group}"}, + {"method": "DELETE", "path": "/v3/domains/{domain_id}/config/{group}/{option}"}, + ], + ), + base.APIRule( + name="identity:get_domain_config_default", + check_str=("role:reader and system_scope:all"), + description="Get domain configuration default for either a domain, " + "specific group or a specific option in a group.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/domains/config/default"}, + {"method": "HEAD", "path": "/v3/domains/config/default"}, + {"method": "GET", "path": "/v3/domains/config/{group}/default"}, + {"method": "HEAD", "path": "/v3/domains/config/{group}/default"}, + {"method": "GET", "path": "/v3/domains/config/{group}/{option}/default"}, + {"method": "HEAD", "path": "/v3/domains/config/{group}/{option}/default"}, + ], + ), + base.APIRule( + name="identity:ec2_get_credential", + check_str=("(role:reader and system_scope:all) or user_id:%(target.credential.user_id)s"), + description="Show ec2 credential details.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/users/{user_id}/credentials/OS-EC2/{credential_id}"}, + ], + ), + base.APIRule( + name="identity:ec2_list_credentials", + check_str=("(role:reader and system_scope:all) or rule:owner"), + description="List ec2 credentials.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v3/users/{user_id}/credentials/OS-EC2"}], + ), + base.APIRule( + name="identity:ec2_create_credential", + check_str=("(role:admin and system_scope:all) or rule:owner"), + description="Create ec2 credential.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/v3/users/{user_id}/credentials/OS-EC2"}], + ), + base.APIRule( + name="identity:ec2_delete_credential", + check_str=("(role:admin and system_scope:all) or user_id:%(target.credential.user_id)s"), + description="Delete ec2 credential.", + scope_types=["system", "project"], + operations=[ + { + "method": "DELETE", + "path": "/v3/users/{user_id}/credentials/OS-EC2/{credential_id}", + }, + ], + ), + base.APIRule( + name="identity:get_endpoint", + check_str=("role:reader and system_scope:all"), + description="Show endpoint details.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/endpoints/{endpoint_id}"}], + ), + base.APIRule( + name="identity:list_endpoints", + check_str=("role:reader and system_scope:all"), + description="List endpoints.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/endpoints"}], + ), + base.APIRule( + name="identity:create_endpoint", + check_str=("role:admin and system_scope:all"), + description="Create endpoint.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/endpoints"}], + ), + base.APIRule( + name="identity:update_endpoint", + check_str=("role:admin and system_scope:all"), + description="Update endpoint.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/endpoints/{endpoint_id}"}], + ), + base.APIRule( + name="identity:delete_endpoint", + check_str=("role:admin and system_scope:all"), + description="Delete endpoint.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/endpoints/{endpoint_id}"}], + ), + base.APIRule( + name="identity:create_endpoint_group", + check_str=("role:admin and system_scope:all"), + description="Create endpoint group.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/OS-EP-FILTER/endpoint_groups"}], + ), + base.APIRule( + name="identity:list_endpoint_groups", + check_str=("role:reader and system_scope:all"), + description="List endpoint groups.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/OS-EP-FILTER/endpoint_groups"}], + ), + base.APIRule( + name="identity:get_endpoint_group", + check_str=("role:reader and system_scope:all"), + description="Get endpoint group.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}"}, + {"method": "HEAD", "path": "/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}"}, + ], + ), + base.APIRule( + name="identity:update_endpoint_group", + check_str=("role:admin and system_scope:all"), + description="Update endpoint group.", + scope_types=["system"], + operations=[ + {"method": "PATCH", "path": "/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}"}, + ], + ), + base.APIRule( + name="identity:delete_endpoint_group", + check_str=("role:admin and system_scope:all"), + description="Delete endpoint group.", + scope_types=["system"], + operations=[ + {"method": "DELETE", "path": "/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}"}, + ], + ), + base.APIRule( + name="identity:list_projects_associated_with_endpoint_group", + check_str=("role:reader and system_scope:all"), + description="List all projects associated with a specific endpoint group.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects", + }, + ], + ), + base.APIRule( + name="identity:list_endpoints_associated_with_endpoint_group", + check_str=("role:reader and system_scope:all"), + description="List all endpoints associated with an endpoint group.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/endpoints", + }, + ], + ), + base.APIRule( + name="identity:get_endpoint_group_in_project", + check_str=("role:reader and system_scope:all"), + description="Check if an endpoint group is associated with a project.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/OS-EP-FILTER/endpoint_groups/" + "{endpoint_group_id}/projects/{project_id}", + }, + { + "method": "HEAD", + "path": "/v3/OS-EP-FILTER/endpoint_groups/" + "{endpoint_group_id}/projects/{project_id}", + }, + ], + ), + base.APIRule( + name="identity:list_endpoint_groups_for_project", + check_str=("role:reader and system_scope:all"), + description="List endpoint groups associated with a specific project.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-EP-FILTER/projects/{project_id}/endpoint_groups"}, + ], + ), + base.APIRule( + name="identity:add_endpoint_group_to_project", + check_str=("role:admin and system_scope:all"), + description="Allow a project to access an endpoint group.", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/v3/OS-EP-FILTER/endpoint_groups/" + "{endpoint_group_id}/projects/{project_id}", + }, + ], + ), + base.APIRule( + name="identity:remove_endpoint_group_from_project", + check_str=("role:admin and system_scope:all"), + description="Remove endpoint group from project.", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/v3/OS-EP-FILTER/endpoint_groups/" + "{endpoint_group_id}/projects/{project_id}", + }, + ], + ), + base.APIRule( + name="identity:check_grant", + check_str=( + "(role:reader and system_scope:all) " + "or ((role:reader and domain_id:%(target.user.domain_id)s " + "and domain_id:%(target.project.domain_id)s) " + "or (role:reader and domain_id:%(target.user.domain_id)s " + "and domain_id:%(target.domain.id)s) " + "or (role:reader and domain_id:%(target.group.domain_id)s " + "and domain_id:%(target.project.domain_id)s) " + "or (role:reader and domain_id:%(target.group.domain_id)s " + "and domain_id:%(target.domain.id)s)) " + "and (domain_id:%(target.role.domain_id)s " + "or None:%(target.role.domain_id)s)" + ), + description="Check a role grant between a target and an actor. " + "A target can be either a domain or a project. " + "An actor can be either a user or a group. " + "These terms also apply to the OS-INHERIT APIs, " + "where grants on the target are inherited to all projects " + "in the subtree, if applicable.", + scope_types=["system", "domain"], + operations=[ + { + "method": "HEAD", + "path": "/v3/projects/{project_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "GET", + "path": "/v3/projects/{project_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "HEAD", + "path": "/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "GET", + "path": "/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "HEAD", + "path": "/v3/domains/{domain_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "GET", + "path": "/v3/domains/{domain_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "HEAD", + "path": "/v3/domains/{domain_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "GET", + "path": "/v3/domains/{domain_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "HEAD", + "path": "/v3/OS-INHERIT/projects/{project_id}/users/" + "{user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "GET", + "path": "/v3/OS-INHERIT/projects/{project_id}/users/" + "{user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "HEAD", + "path": "/v3/OS-INHERIT/projects/{project_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "GET", + "path": "/v3/OS-INHERIT/projects/{project_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "HEAD", + "path": "/v3/OS-INHERIT/domains/{domain_id}/users/" + "{user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "GET", + "path": "/v3/OS-INHERIT/domains/{domain_id}/users/" + "{user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "HEAD", + "path": "/v3/OS-INHERIT/domains/{domain_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "GET", + "path": "/v3/OS-INHERIT/domains/{domain_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + ], + ), + base.APIRule( + name="identity:list_grants", + check_str=( + "(role:reader and system_scope:all) or (role:reader " + "and domain_id:%(target.user.domain_id)s " + "and domain_id:%(target.project.domain_id)s) " + "or (role:reader and domain_id:%(target.user.domain_id)s and " + "domain_id:%(target.domain.id)s) or (role:reader and " + "domain_id:%(target.group.domain_id)s and domain_id:%(" + "target.project.domain_id)s) or (role:reader and domain_id:%(" + "target.group.domain_id)s and domain_id:%(target.domain.id)s) " + ), + description="List roles granted to an actor on a target. " + "A target can be either a domain or a project. " + "An actor can be either a user or a group. " + "For the OS-INHERIT APIs, it is possible to " + "list inherited role grants for actors on domains, " + "where grants are inherited to all projects in the " + "specified domain.", + scope_types=["system", "domain"], + operations=[ + {"method": "GET", "path": "/v3/projects/{project_id}/users/{user_id}/roles"}, + {"method": "HEAD", "path": "/v3/projects/{project_id}/users/{user_id}/roles"}, + {"method": "GET", "path": "/v3/projects/{project_id}/groups/{group_id}/roles"}, + {"method": "HEAD", "path": "/v3/projects/{project_id}/groups/{group_id}/roles"}, + {"method": "GET", "path": "/v3/domains/{domain_id}/users/{user_id}/roles"}, + {"method": "HEAD", "path": "/v3/domains/{domain_id}/users/{user_id}/roles"}, + {"method": "GET", "path": "/v3/domains/{domain_id}/groups/{group_id}/roles"}, + {"method": "HEAD", "path": "/v3/domains/{domain_id}/groups/{group_id}/roles"}, + { + "method": "GET", + "path": "/v3/OS-INHERIT/domains/{domain_id}/groups/" + "{group_id}/roles/inherited_to_projects", + }, + { + "method": "GET", + "path": "/v3/OS-INHERIT/domains/{domain_id}/users/" + "{user_id}/roles/inherited_to_projects", + }, + ], + ), + base.APIRule( + name="identity:create_grant", + check_str=( + "(role:admin and system_scope:all) or ((role:admin and " + "domain_id:%(target.user.domain_id)s and domain_id:%(" + "target.project.domain_id)s) or (role:admin and domain_id:%(" + "target.user.domain_id)s and domain_id:%(target.domain.id)s) or " + "(role:admin and domain_id:%(target.group.domain_id)s and " + "domain_id:%(target.project.domain_id)s) or (role:admin and " + "domain_id:%(target.group.domain_id)s and domain_id:%(" + "target.domain.id)s)) and (domain_id:%(target.role.domain_id)s " + "or None:%(target.role.domain_id)s) " + ), + description="Create a role grant between a target and an actor. A " + "target can be either a domain or a project. An actor " + "can be either a user or a group. These terms also apply " + "to the OS-INHERIT APIs, where grants on the target are " + "inherited to all projects in the subtree, " + "if applicable.", + scope_types=["system", "domain"], + operations=[ + { + "method": "PUT", + "path": "/v3/projects/{project_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "PUT", + "path": "/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "PUT", + "path": "/v3/domains/{domain_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "PUT", + "path": "/v3/domains/{domain_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "PUT", + "path": "/v3/OS-INHERIT/projects/{project_id}/users/" + "{user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "PUT", + "path": "/v3/OS-INHERIT/projects/{project_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "PUT", + "path": "/v3/OS-INHERIT/domains/{domain_id}/users/" + "{user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "PUT", + "path": "/v3/OS-INHERIT/domains/{domain_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + ], + ), + base.APIRule( + name="identity:revoke_grant", + check_str=( + "(role:admin and system_scope:all) or ((role:admin and " + "domain_id:%(target.user.domain_id)s and domain_id:%(" + "target.project.domain_id)s) or (role:admin and domain_id:%(" + "target.user.domain_id)s and domain_id:%(target.domain.id)s) or " + "(role:admin and domain_id:%(target.group.domain_id)s and " + "domain_id:%(target.project.domain_id)s) or (role:admin and " + "domain_id:%(target.group.domain_id)s and domain_id:%(" + "target.domain.id)s)) and (domain_id:%(target.role.domain_id)s " + "or None:%(target.role.domain_id)s) " + ), + description="Revoke a role grant between a target and an actor. A " + "target can be either a domain or a project. An actor " + "can be either a user or a group. These terms also apply " + "to the OS-INHERIT APIs, where grants on the target are " + "inherited to all projects in the subtree, " + "if applicable. In that case, revoking the role grant in " + "the target would remove the logical effect of " + "inheriting it to the target's projects subtree.", + scope_types=["system", "domain"], + operations=[ + { + "method": "DELETE", + "path": "/v3/projects/{project_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "DELETE", + "path": "/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "DELETE", + "path": "/v3/domains/{domain_id}/users/{user_id}/roles/{role_id}", + }, + { + "method": "DELETE", + "path": "/v3/domains/{domain_id}/groups/{group_id}/roles/{role_id}", + }, + { + "method": "DELETE", + "path": "/v3/OS-INHERIT/projects/{project_id}/users/{" + "user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "DELETE", + "path": "/v3/OS-INHERIT/projects/{project_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "DELETE", + "path": "/v3/OS-INHERIT/domains/{domain_id}/users/" + "{user_id}/roles/{role_id}/inherited_to_projects", + }, + { + "method": "DELETE", + "path": "/v3/OS-INHERIT/domains/{domain_id}/groups/" + "{group_id}/roles/{role_id}/inherited_to_projects", + }, + ], + ), + base.APIRule( + name="identity:list_system_grants_for_user", + check_str=("role:reader and system_scope:all"), + description="List all grants a specific user has on the system.", + scope_types=["system"], + operations=[ + {"method": "HEAD", "path": "/v3/system/users/{user_id}/roles"}, + {"method": "GET", "path": "/v3/system/users/{user_id}/roles"}, + ], + ), + base.APIRule( + name="identity:check_system_grant_for_user", + check_str=("role:reader and system_scope:all"), + description="Check if a user has a role on the system.", + scope_types=["system"], + operations=[ + {"method": "HEAD", "path": "/v3/system/users/{user_id}/roles/{role_id}"}, + {"method": "GET", "path": "/v3/system/users/{user_id}/roles/{role_id}"}, + ], + ), + base.APIRule( + name="identity:create_system_grant_for_user", + check_str=("role:admin and system_scope:all"), + description="Grant a user a role on the system.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/v3/system/users/{user_id}/roles/{role_id}"}], + ), + base.APIRule( + name="identity:revoke_system_grant_for_user", + check_str=("role:admin and system_scope:all"), + description="Remove a role from a user on the system.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/system/users/{user_id}/roles/{role_id}"}], + ), + base.APIRule( + name="identity:list_system_grants_for_group", + check_str=("role:reader and system_scope:all"), + description="List all grants a specific group has on the system.", + scope_types=["system"], + operations=[ + {"method": "HEAD", "path": "/v3/system/groups/{group_id}/roles"}, + {"method": "GET", "path": "/v3/system/groups/{group_id}/roles"}, + ], + ), + base.APIRule( + name="identity:check_system_grant_for_group", + check_str=("role:reader and system_scope:all"), + description="Check if a group has a role on the system.", + scope_types=["system"], + operations=[ + {"method": "HEAD", "path": "/v3/system/groups/{group_id}/roles/{role_id}"}, + {"method": "GET", "path": "/v3/system/groups/{group_id}/roles/{role_id}"}, + ], + ), + base.APIRule( + name="identity:create_system_grant_for_group", + check_str=("role:admin and system_scope:all"), + description="Grant a group a role on the system.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/v3/system/groups/{group_id}/roles/{role_id}"}], + ), + base.APIRule( + name="identity:revoke_system_grant_for_group", + check_str=("role:admin and system_scope:all"), + description="Remove a role from a group on the system.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/system/groups/{group_id}/roles/{role_id}"}], + ), + base.APIRule( + name="identity:get_group", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.group.domain_id)s)" + ), + description="Show group details.", + scope_types=["system", "domain"], + operations=[ + {"method": "GET", "path": "/v3/groups/{group_id}"}, + {"method": "HEAD", "path": "/v3/groups/{group_id}"}, + ], + ), + base.APIRule( + name="identity:list_groups", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.group.domain_id)s)" + ), + description="List groups.", + scope_types=["system", "domain"], + operations=[ + {"method": "GET", "path": "/v3/groups"}, + {"method": "HEAD", "path": "/v3/groups"}, + ], + ), + base.APIRule( + name="identity:list_groups_for_user", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.user.domain_id)s) " + "or user_id:%(user_id)s" + ), + description="List groups to which a user belongs.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/users/{user_id}/groups"}, + {"method": "HEAD", "path": "/v3/users/{user_id}/groups"}, + ], + ), + base.APIRule( + name="identity:create_group", + check_str=( + "(role:admin and system_scope:all) or (role:admin " + "and domain_id:%(target.group.domain_id)s)" + ), + description="Create group.", + scope_types=["system", "domain"], + operations=[{"method": "POST", "path": "/v3/groups"}], + ), + base.APIRule( + name="identity:update_group", + check_str=( + "(role:admin and system_scope:all) " + "or (role:admin and domain_id:%(target.group.domain_id)s)" + ), + description="Update group.", + scope_types=["system", "domain"], + operations=[{"method": "PATCH", "path": "/v3/groups/{group_id}"}], + ), + base.APIRule( + name="identity:delete_group", + check_str=( + "(role:admin and system_scope:all) " + "or (role:admin and domain_id:%(target.group.domain_id)s)" + ), + description="Delete group.", + scope_types=["system", "domain"], + operations=[{"method": "DELETE", "path": "/v3/groups/{group_id}"}], + ), + base.APIRule( + name="identity:list_users_in_group", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.group.domain_id)s)" + ), + description="List members of a specific group.", + scope_types=["system", "domain"], + operations=[ + {"method": "GET", "path": "/v3/groups/{group_id}/users"}, + {"method": "HEAD", "path": "/v3/groups/{group_id}/users"}, + ], + ), + base.APIRule( + name="identity:remove_user_from_group", + check_str=( + "(role:admin and system_scope:all) " + "or (role:admin and domain_id:%(target.group.domain_id)s " + "and domain_id:%(target.user.domain_id)s)" + ), + description="Remove user from group.", + scope_types=["system", "domain"], + operations=[{"method": "DELETE", "path": "/v3/groups/{group_id}/users/{user_id}"}], + ), + base.APIRule( + name="identity:check_user_in_group", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.group.domain_id)s " + "and domain_id:%(target.user.domain_id)s)" + ), + description="Check whether a user is a member of a group.", + scope_types=["system", "domain"], + operations=[ + {"method": "HEAD", "path": "/v3/groups/{group_id}/users/{user_id}"}, + {"method": "GET", "path": "/v3/groups/{group_id}/users/{user_id}"}, + ], + ), + base.APIRule( + name="identity:add_user_to_group", + check_str=( + "(role:admin and system_scope:all) or (role:admin " + "and domain_id:%(target.group.domain_id)s " + "and domain_id:%(target.user.domain_id)s)" + ), + description="Add user to group.", + scope_types=["system", "domain"], + operations=[{"method": "PUT", "path": "/v3/groups/{group_id}/users/{user_id}"}], + ), + base.APIRule( + name="identity:create_identity_provider", + check_str=("role:admin and system_scope:all"), + description="Create identity provider.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/v3/OS-FEDERATION/identity_providers/{idp_id}"}], + ), + base.APIRule( + name="identity:list_identity_providers", + check_str=("role:reader and system_scope:all"), + description="List identity providers.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-FEDERATION/identity_providers"}, + {"method": "HEAD", "path": "/v3/OS-FEDERATION/identity_providers"}, + ], + ), + base.APIRule( + name="identity:get_identity_provider", + check_str=("role:reader and system_scope:all"), + description="Get identity provider.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-FEDERATION/identity_providers/{idp_id}"}, + {"method": "HEAD", "path": "/v3/OS-FEDERATION/identity_providers/{idp_id}"}, + ], + ), + base.APIRule( + name="identity:update_identity_provider", + check_str=("role:admin and system_scope:all"), + description="Update identity provider.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/OS-FEDERATION/identity_providers/{idp_id}"}], + ), + base.APIRule( + name="identity:delete_identity_provider", + check_str=("role:admin and system_scope:all"), + description="Delete identity provider.", + scope_types=["system"], + operations=[ + {"method": "DELETE", "path": "/v3/OS-FEDERATION/identity_providers/{idp_id}"}, + ], + ), + base.APIRule( + name="identity:get_implied_role", + check_str=("role:reader and system_scope:all"), + description="Get information about an association between two roles. " + "When a relationship exists between a prior role and " + "an implied role and the prior role is assigned to a " + "user, the user also assumes the implied role.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/roles/{prior_role_id}/implies/{implied_role_id}"}, + ], + ), + base.APIRule( + name="identity:list_implied_roles", + check_str=("role:reader and system_scope:all"), + description="List associations between two roles. When a " + "relationship exists between a prior role and an implied " + "role and the prior role is assigned to a user, the user " + "also assumes the implied role. This will return all the " + "implied roles that would be assumed by the user who " + "gets the specified prior role.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/roles/{prior_role_id}/implies"}, + {"method": "HEAD", "path": "/v3/roles/{prior_role_id}/implies"}, + ], + ), + base.APIRule( + name="identity:create_implied_role", + check_str=("role:admin and system_scope:all"), + description="Create an association between two roles. When a " + "relationship exists between a prior role and an implied " + "role and the prior role is assigned to a user, the user " + "also assumes the implied role.", + scope_types=["system"], + operations=[ + {"method": "PUT", "path": "/v3/roles/{prior_role_id}/implies/{implied_role_id}"}, + ], + ), + base.APIRule( + name="identity:delete_implied_role", + check_str=("role:admin and system_scope:all"), + description="Delete the association between two roles. When a " + "relationship exists between a prior role and an implied " + "role and the prior role is assigned to a user, the user " + "also assumes the implied role. Removing the association " + "will cause that effect to be eliminated.", + scope_types=["system"], + operations=[ + {"method": "DELETE", "path": "/v3/roles/{prior_role_id}/implies/{implied_role_id}"}, + ], + ), + base.APIRule( + name="identity:list_role_inference_rules", + check_str=("role:reader and system_scope:all"), + description="List all associations between two roles in the system. " + "When a relationship exists between a prior role and an " + "implied role and the prior role is assigned to a user, " + "the user also assumes the implied role.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/role_inferences"}, + {"method": "HEAD", "path": "/v3/role_inferences"}, + ], + ), + base.APIRule( + name="identity:check_implied_role", + check_str=("role:reader and system_scope:all"), + description="Check an association between two roles. When a " + "relationship exists between a prior role and an implied " + "role and the prior role is assigned to a user, the user " + "also assumes the implied role.", + scope_types=["system"], + operations=[ + {"method": "HEAD", "path": "/v3/roles/{prior_role_id}/implies/{implied_role_id}"}, + ], + ), + base.APIRule( + name="identity:get_limit_model", + check_str=(""), + description="Get limit enforcement model.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/limits/model"}, + {"method": "HEAD", "path": "/v3/limits/model"}, + ], + ), + base.APIRule( + name="identity:get_limit", + check_str=( + "(role:reader and system_scope:all) or (domain_id:%(" + "target.limit.domain.id)s or domain_id:%(" + "target.limit.project.domain_id)s) or (project_id:%(" + "target.limit.project_id)s and not None:%(" + "target.limit.project_id)s) " + ), + description="Show limit details.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/limits/{limit_id}"}, + {"method": "HEAD", "path": "/v3/limits/{limit_id}"}, + ], + ), + base.APIRule( + name="identity:list_limits", + check_str=(""), + description="List limits.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/limits"}, + {"method": "HEAD", "path": "/v3/limits"}, + ], + ), + base.APIRule( + name="identity:create_limits", + check_str=("role:admin and system_scope:all"), + description="Create limits.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/limits"}], + ), + base.APIRule( + name="identity:update_limit", + check_str=("role:admin and system_scope:all"), + description="Update limit.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/limits/{limit_id}"}], + ), + base.APIRule( + name="identity:delete_limit", + check_str=("role:admin and system_scope:all"), + description="Delete limit.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/limits/{limit_id}"}], + ), + base.APIRule( + name="identity:create_mapping", + check_str=("role:admin and system_scope:all"), + description="Create a new federated mapping containing one or more sets of rules.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/v3/OS-FEDERATION/mappings/{mapping_id}"}], + ), + base.APIRule( + name="identity:get_mapping", + check_str=("role:reader and system_scope:all"), + description="Get a federated mapping.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-FEDERATION/mappings/{mapping_id}"}, + {"method": "HEAD", "path": "/v3/OS-FEDERATION/mappings/{mapping_id}"}, + ], + ), + base.APIRule( + name="identity:list_mappings", + check_str=("role:reader and system_scope:all"), + description="List federated mappings.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-FEDERATION/mappings"}, + {"method": "HEAD", "path": "/v3/OS-FEDERATION/mappings"}, + ], + ), + base.APIRule( + name="identity:delete_mapping", + check_str=("role:admin and system_scope:all"), + description="Delete a federated mapping.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/OS-FEDERATION/mappings/{mapping_id}"}], + ), + base.APIRule( + name="identity:update_mapping", + check_str=("role:admin and system_scope:all"), + description="Update a federated mapping.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/OS-FEDERATION/mappings/{mapping_id}"}], + ), + base.APIRule( + name="identity:get_policy", + check_str=("role:reader and system_scope:all"), + description="Show policy details.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/policies/{policy_id}"}], + ), + base.APIRule( + name="identity:list_policies", + check_str=("role:reader and system_scope:all"), + description="List policies.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/policies"}], + ), + base.APIRule( + name="identity:create_policy", + check_str=("role:admin and system_scope:all"), + description="Create policy.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/policies"}], + ), + base.APIRule( + name="identity:update_policy", + check_str=("role:admin and system_scope:all"), + description="Update policy.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/policies/{policy_id}"}], + ), + base.APIRule( + name="identity:delete_policy", + check_str=("role:admin and system_scope:all"), + description="Delete policy.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/policies/{policy_id}"}], + ), + base.APIRule( + name="identity:create_policy_association_for_endpoint", + check_str=("role:admin and system_scope:all"), + description="Associate a policy to a specific endpoint.", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints/{endpoint_id}", + }, + ], + ), + base.APIRule( + name="identity:check_policy_association_for_endpoint", + check_str=("role:reader and system_scope:all"), + description="Check policy association for endpoint.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints/{endpoint_id}", + }, + { + "method": "HEAD", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints/{endpoint_id}", + }, + ], + ), + base.APIRule( + name="identity:delete_policy_association_for_endpoint", + check_str=("role:admin and system_scope:all"), + description="Delete policy association for endpoint.", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints/{endpoint_id}", + }, + ], + ), + base.APIRule( + name="identity:create_policy_association_for_service", + check_str=("role:admin and system_scope:all"), + description="Associate a policy to a specific service.", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}", + }, + ], + ), + base.APIRule( + name="identity:check_policy_association_for_service", + check_str=("role:reader and system_scope:all"), + description="Check policy association for service.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}", + }, + { + "method": "HEAD", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}", + }, + ], + ), + base.APIRule( + name="identity:delete_policy_association_for_service", + check_str=("role:admin and system_scope:all"), + description="Delete policy association for service.", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}", + }, + ], + ), + base.APIRule( + name="identity:create_policy_association_for_region_and_service", + check_str=("role:admin and system_scope:all"), + description="Associate a policy to a specific region and service combination.", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/" + "services/{service_id}/regions/{region_id}", + }, + ], + ), + base.APIRule( + name="identity:check_policy_association_for_region_and_service", + check_str=("role:reader and system_scope:all"), + description="Check policy association for region and service.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/" + "services/{service_id}/regions/{region_id}", + }, + { + "method": "HEAD", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/" + "services/{service_id}/regions/{region_id}", + }, + ], + ), + base.APIRule( + name="identity:delete_policy_association_for_region_and_service", + check_str=("role:admin and system_scope:all"), + description="Delete policy association for region and service.", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/" + "services/{service_id}/regions/{region_id}", + }, + ], + ), + base.APIRule( + name="identity:get_policy_for_endpoint", + check_str=("role:reader and system_scope:all"), + description="Get policy for endpoint.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/endpoints/{endpoint_id}/OS-ENDPOINT-POLICY/policy"}, + {"method": "HEAD", "path": "/v3/endpoints/{endpoint_id}/OS-ENDPOINT-POLICY/policy"}, + ], + ), + base.APIRule( + name="identity:list_endpoints_for_policy", + check_str=("role:reader and system_scope:all"), + description="List endpoints for policy.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints"}, + ], + ), + base.APIRule( + name="identity:get_project", + check_str=( + "(role:reader and system_scope:all) or (role:reader " + "and domain_id:%(target.project.domain_id)s) " + "or project_id:%(target.project.id)s" + ), + description="Show project details.", + scope_types=["system", "domain", "project"], + operations=[{"method": "GET", "path": "/v3/projects/{project_id}"}], + ), + base.APIRule( + name="identity:list_projects", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.domain_id)s)" + ), + description="List projects.", + scope_types=["system", "domain"], + operations=[{"method": "GET", "path": "/v3/projects"}], + ), + base.APIRule( + name="identity:list_user_projects", + check_str=( + "(role:reader and system_scope:all) or (role:reader " + "and domain_id:%(target.user.domain_id)s) " + "or user_id:%(target.user.id)s" + ), + description="List projects for user.", + scope_types=["system", "domain", "project"], + operations=[{"method": "GET", "path": "/v3/users/{user_id}/projects"}], + ), + base.APIRule( + name="identity:create_project", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "domain_id:%(target.project.domain_id)s)" + ), + description="Create project.", + scope_types=["system", "domain"], + operations=[{"method": "POST", "path": "/v3/projects"}], + ), + base.APIRule( + name="identity:update_project", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "domain_id:%(target.project.domain_id)s)" + ), + description="Update project.", + scope_types=["system", "domain"], + operations=[{"method": "PATCH", "path": "/v3/projects/{project_id}"}], + ), + base.APIRule( + name="identity:delete_project", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "domain_id:%(target.project.domain_id)s) " + ), + description="Delete project.", + scope_types=["system", "domain"], + operations=[{"method": "DELETE", "path": "/v3/projects/{project_id}"}], + ), + base.APIRule( + name="identity:list_project_tags", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "domain_id:%(target.project.domain_id)s) or project_id:%(" + "target.project.id)s " + ), + description="List tags for a project.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/projects/{project_id}/tags"}, + {"method": "HEAD", "path": "/v3/projects/{project_id}/tags"}, + ], + ), + base.APIRule( + name="identity:get_project_tag", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "domain_id:%(target.project.domain_id)s) or project_id:%(" + "target.project.id)s " + ), + description="Check if project contains a tag.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/projects/{project_id}/tags/{value}"}, + {"method": "HEAD", "path": "/v3/projects/{project_id}/tags/{value}"}, + ], + ), + base.APIRule( + name="identity:update_project_tags", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "domain_id:%(target.project.domain_id)s) or (role:admin and " + "project_id:%(target.project.id)s) " + ), + description="Replace all tags on a project with the new set of tags.", + scope_types=["system", "domain", "project"], + operations=[{"method": "PUT", "path": "/v3/projects/{project_id}/tags"}], + ), + base.APIRule( + name="identity:create_project_tag", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "domain_id:%(target.project.domain_id)s) or (role:admin and " + "project_id:%(target.project.id)s) " + ), + description="Add a single tag to a project.", + scope_types=["system", "domain", "project"], + operations=[{"method": "PUT", "path": "/v3/projects/{project_id}/tags/{value}"}], + ), + base.APIRule( + name="identity:delete_project_tags", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "domain_id:%(target.project.domain_id)s) or (role:admin and " + "project_id:%(target.project.id)s) " + ), + description="Remove all tags from a project.", + scope_types=["system", "domain", "project"], + operations=[{"method": "DELETE", "path": "/v3/projects/{project_id}/tags"}], + ), + base.APIRule( + name="identity:delete_project_tag", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "domain_id:%(target.project.domain_id)s) or (role:admin and " + "project_id:%(target.project.id)s) " + ), + description="Delete a specified tag from project.", + scope_types=["system", "domain", "project"], + operations=[{"method": "DELETE", "path": "/v3/projects/{project_id}/tags/{value}"}], + ), + base.APIRule( + name="identity:list_projects_for_endpoint", + check_str=("role:reader and system_scope:all"), + description="List projects allowed to access an endpoint.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-EP-FILTER/endpoints/{endpoint_id}/projects"}, + ], + ), + base.APIRule( + name="identity:add_endpoint_to_project", + check_str=("role:admin and system_scope:all"), + description="Allow project to access an endpoint.", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/v3/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}", + }, + ], + ), + base.APIRule( + name="identity:check_endpoint_in_project", + check_str=("role:reader and system_scope:all"), + description="Check if a project is allowed to access an endpoint.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}", + }, + { + "method": "HEAD", + "path": "/v3/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}", + }, + ], + ), + base.APIRule( + name="identity:list_endpoints_for_project", + check_str=("role:reader and system_scope:all"), + description="List the endpoints a project is allowed to access.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-EP-FILTER/projects/{project_id}/endpoints"}, + ], + ), + base.APIRule( + name="identity:remove_endpoint_from_project", + check_str=("role:admin and system_scope:all"), + description="Remove access to an endpoint from a project that has " + "previously been given explicit access.", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/v3/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}", + }, + ], + ), + base.APIRule( + name="identity:create_protocol", + check_str=("role:admin and system_scope:all"), + description="Create federated protocol.", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/v3/OS-FEDERATION/identity_providers/" + "{idp_id}/protocols/{protocol_id}", + }, + ], + ), + base.APIRule( + name="identity:update_protocol", + check_str=("role:admin and system_scope:all"), + description="Update federated protocol.", + scope_types=["system"], + operations=[ + { + "method": "PATCH", + "path": "/v3/OS-FEDERATION/identity_providers/" + "{idp_id}/protocols/{protocol_id}", + }, + ], + ), + base.APIRule( + name="identity:get_protocol", + check_str=("role:reader and system_scope:all"), + description="Get federated protocol.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/OS-FEDERATION/identity_providers/" + "{idp_id}/protocols/{protocol_id}", + }, + ], + ), + base.APIRule( + name="identity:list_protocols", + check_str=("role:reader and system_scope:all"), + description="List federated protocols.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/OS-FEDERATION/identity_providers/{idp_id}/protocols", + }, + ], + ), + base.APIRule( + name="identity:delete_protocol", + check_str=("role:admin and system_scope:all"), + description="Delete federated protocol.", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/v3/OS-FEDERATION/identity_providers" + "/{idp_id}/protocols/{protocol_id}", + }, + ], + ), + base.APIRule( + name="identity:get_region", + check_str=(""), + description="Show region details.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/regions/{region_id}"}, + {"method": "HEAD", "path": "/v3/regions/{region_id}"}, + ], + ), + base.APIRule( + name="identity:list_regions", + check_str=(""), + description="List regions.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/regions"}, + {"method": "HEAD", "path": "/v3/regions"}, + ], + ), + base.APIRule( + name="identity:create_region", + check_str=("role:admin and system_scope:all"), + description="Create region.", + scope_types=["system"], + operations=[ + {"method": "POST", "path": "/v3/regions"}, + {"method": "PUT", "path": "/v3/regions/{region_id}"}, + ], + ), + base.APIRule( + name="identity:update_region", + check_str=("role:admin and system_scope:all"), + description="Update region.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/regions/{region_id}"}], + ), + base.APIRule( + name="identity:delete_region", + check_str=("role:admin and system_scope:all"), + description="Delete region.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/regions/{region_id}"}], + ), + base.APIRule( + name="identity:get_registered_limit", + check_str=(""), + description="Show registered limit details.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/registered_limits/{registered_limit_id}"}, + {"method": "HEAD", "path": "/v3/registered_limits/{registered_limit_id}"}, + ], + ), + base.APIRule( + name="identity:list_registered_limits", + check_str=(""), + description="List registered limits.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/registered_limits"}, + {"method": "HEAD", "path": "/v3/registered_limits"}, + ], + ), + base.APIRule( + name="identity:create_registered_limits", + check_str=("role:admin and system_scope:all"), + description="Create registered limits.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/registered_limits"}], + ), + base.APIRule( + name="identity:update_registered_limit", + check_str=("role:admin and system_scope:all"), + description="Update registered limit.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/registered_limits/{registered_limit_id}"}], + ), + base.APIRule( + name="identity:delete_registered_limit", + check_str=("role:admin and system_scope:all"), + description="Delete registered limit.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/registered_limits/{registered_limit_id}"}], + ), + base.APIRule( + name="identity:list_revoke_events", + check_str=("rule:service_or_admin"), + description="List revocation events.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/OS-REVOKE/events"}], + ), + base.APIRule( + name="identity:get_role", + check_str=("role:reader and system_scope:all"), + description="Show role details.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/roles/{role_id}"}, + {"method": "HEAD", "path": "/v3/roles/{role_id}"}, + ], + ), + base.APIRule( + name="identity:list_roles", + check_str=("role:reader and system_scope:all"), + description="List roles.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/roles"}, + {"method": "HEAD", "path": "/v3/roles"}, + ], + ), + base.APIRule( + name="identity:create_role", + check_str=("role:admin and system_scope:all"), + description="Create role.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/roles"}], + ), + base.APIRule( + name="identity:update_role", + check_str=("role:admin and system_scope:all"), + description="Update role.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/roles/{role_id}"}], + ), + base.APIRule( + name="identity:delete_role", + check_str=("role:admin and system_scope:all"), + description="Delete role.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/roles/{role_id}"}], + ), + base.APIRule( + name="identity:get_domain_role", + check_str=("role:reader and system_scope:all"), + description="Show domain role.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/roles/{role_id}"}, + {"method": "HEAD", "path": "/v3/roles/{role_id}"}, + ], + ), + base.APIRule( + name="identity:list_domain_roles", + check_str=("role:reader and system_scope:all"), + description="List domain roles.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/roles?domain_id={domain_id}"}, + {"method": "HEAD", "path": "/v3/roles?domain_id={domain_id}"}, + ], + ), + base.APIRule( + name="identity:create_domain_role", + check_str=("role:admin and system_scope:all"), + description="Create domain role.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/roles"}], + ), + base.APIRule( + name="identity:update_domain_role", + check_str=("role:admin and system_scope:all"), + description="Update domain role.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/roles/{role_id}"}], + ), + base.APIRule( + name="identity:delete_domain_role", + check_str=("role:admin and system_scope:all"), + description="Delete domain role.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/roles/{role_id}"}], + ), + base.APIRule( + name="identity:list_role_assignments", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.domain_id)s)" + ), + description="List role assignments.", + scope_types=["system", "domain"], + operations=[ + {"method": "GET", "path": "/v3/role_assignments"}, + {"method": "HEAD", "path": "/v3/role_assignments"}, + ], + ), + base.APIRule( + name="identity:list_role_assignments_for_tree", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and domain_id:%(target.project.domain_id)s) " + "or (role:admin and project_id:%(target.project.id)s)" + ), + description="List all role assignments for a given tree of hierarchical projects.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/role_assignments?include_subtree"}, + {"method": "HEAD", "path": "/v3/role_assignments?include_subtree"}, + ], + ), + base.APIRule( + name="identity:get_service", + check_str=("role:reader and system_scope:all"), + description="Show service details.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/services/{service_id}"}], + ), + base.APIRule( + name="identity:list_services", + check_str=("role:reader and system_scope:all"), + description="List services.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/v3/services"}], + ), + base.APIRule( + name="identity:create_service", + check_str=("role:admin and system_scope:all"), + description="Create service.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/v3/services"}], + ), + base.APIRule( + name="identity:update_service", + check_str=("role:admin and system_scope:all"), + description="Update service.", + scope_types=["system"], + operations=[{"method": "PATCH", "path": "/v3/services/{service_id}"}], + ), + base.APIRule( + name="identity:delete_service", + check_str=("role:admin and system_scope:all"), + description="Delete service.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/v3/services/{service_id}"}], + ), + base.APIRule( + name="identity:create_service_provider", + check_str=("role:admin and system_scope:all"), + description="Create federated service provider.", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/v3/OS-FEDERATION/service_providers/{service_provider_id}", + }, + ], + ), + base.APIRule( + name="identity:list_service_providers", + check_str=("role:reader and system_scope:all"), + description="List federated service providers.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-FEDERATION/service_providers"}, + {"method": "HEAD", "path": "/v3/OS-FEDERATION/service_providers"}, + ], + ), + base.APIRule( + name="identity:get_service_provider", + check_str=("role:reader and system_scope:all"), + description="Get federated service provider.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/v3/OS-FEDERATION/service_providers/{service_provider_id}", + }, + { + "method": "HEAD", + "path": "/v3/OS-FEDERATION/service_providers/{service_provider_id}", + }, + ], + ), + base.APIRule( + name="identity:update_service_provider", + check_str=("role:admin and system_scope:all"), + description="Update federated service provider.", + scope_types=["system"], + operations=[ + { + "method": "PATCH", + "path": "/v3/OS-FEDERATION/service_providers/{service_provider_id}", + }, + ], + ), + base.APIRule( + name="identity:delete_service_provider", + check_str=("role:admin and system_scope:all"), + description="Delete federated service provider.", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/v3/OS-FEDERATION/service_providers/{service_provider_id}", + }, + ], + ), + base.APIRule( + name="identity:revocation_list", + check_str=("rule:service_or_admin"), + description="List revoked PKI tokens.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v3/auth/tokens/OS-PKI/revoked"}], + ), + base.APIRule( + name="identity:check_token", + check_str=("(role:reader and system_scope:all) or rule:token_subject"), + description="Check a token.", + scope_types=["system", "domain", "project"], + operations=[{"method": "HEAD", "path": "/v3/auth/tokens"}], + ), + base.APIRule( + name="identity:validate_token", + check_str=( + "(role:reader and system_scope:all) or rule:service_role or rule:token_subject " + ), + description="Validate a token.", + scope_types=["system", "domain", "project"], + operations=[{"method": "GET", "path": "/v3/auth/tokens"}], + ), + base.APIRule( + name="identity:revoke_token", + check_str=("(role:admin and system_scope:all) or rule:token_subject"), + description="Revoke a token.", + scope_types=["system", "domain", "project"], + operations=[{"method": "DELETE", "path": "/v3/auth/tokens"}], + ), + base.APIRule( + name="identity:create_trust", + check_str=("user_id:%(trust.trustor_user_id)s"), + description="Create trust.", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v3/OS-TRUST/trusts"}], + ), + base.APIRule( + name="identity:list_trusts", + check_str=("role:reader and system_scope:all"), + description="List trusts.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v3/OS-TRUST/trusts"}, + {"method": "HEAD", "path": "/v3/OS-TRUST/trusts"}, + ], + ), + base.APIRule( + name="identity:list_trusts_for_trustor", + check_str=( + "role:reader and system_scope:all or user_id:%(target.trust.trustor_user_id)s " + ), + description="List trusts for trustor.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/OS-TRUST/trusts?trustor_user_id={trustor_user_id}"}, + {"method": "HEAD", "path": "/v3/OS-TRUST/trusts?trustor_user_id={trustor_user_id}"}, + ], + ), + base.APIRule( + name="identity:list_trusts_for_trustee", + check_str=( + "role:reader and system_scope:all or user_id:%(target.trust.trustee_user_id)s " + ), + description="List trusts for trustee.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/OS-TRUST/trusts?trustee_user_id={trustee_user_id}"}, + {"method": "HEAD", "path": "/v3/OS-TRUST/trusts?trustee_user_id={trustee_user_id}"}, + ], + ), + base.APIRule( + name="identity:list_roles_for_trust", + check_str=( + "role:reader and system_scope:all or user_id:%(" + "target.trust.trustor_user_id)s or user_id:%(" + "target.trust.trustee_user_id)s " + ), + description="List roles delegated by a trust.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/OS-TRUST/trusts/{trust_id}/roles"}, + {"method": "HEAD", "path": "/v3/OS-TRUST/trusts/{trust_id}/roles"}, + ], + ), + base.APIRule( + name="identity:get_role_for_trust", + check_str=( + "role:reader and system_scope:all or user_id:%(" + "target.trust.trustor_user_id)s or user_id:%(" + "target.trust.trustee_user_id)s " + ), + description="Check if trust delegates a particular role.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}"}, + {"method": "HEAD", "path": "/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}"}, + ], + ), + base.APIRule( + name="identity:delete_trust", + check_str=("role:admin and system_scope:all or user_id:%(target.trust.trustor_user_id)s"), + description="Revoke trust.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/v3/OS-TRUST/trusts/{trust_id}"}], + ), + base.APIRule( + name="identity:get_trust", + check_str=( + "role:reader and system_scope:all or user_id:%(" + "target.trust.trustor_user_id)s or user_id:%(" + "target.trust.trustee_user_id)s " + ), + description="Get trust.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/v3/OS-TRUST/trusts/{trust_id}"}, + {"method": "HEAD", "path": "/v3/OS-TRUST/trusts/{trust_id}"}, + ], + ), + base.APIRule( + name="identity:get_user", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "token.domain.id:%(target.user.domain_id)s) or user_id:%(" + "target.user.id)s " + ), + description="Show user details.", + scope_types=["system", "domain", "project"], + operations=[ + {"method": "GET", "path": "/v3/users/{user_id}"}, + {"method": "HEAD", "path": "/v3/users/{user_id}"}, + ], + ), + base.APIRule( + name="identity:list_users", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "domain_id:%(target.domain_id)s) " + ), + description="List users.", + scope_types=["system", "domain"], + operations=[ + {"method": "GET", "path": "/v3/users"}, + {"method": "HEAD", "path": "/v3/users"}, + ], + ), + base.APIRule( + name="identity:list_projects_for_user", + check_str=(""), + description="List all projects a user has access to via role assignments.", + scope_types=["project"], + operations=[{"method": "GET", "path": " /v3/auth/projects"}], + ), + base.APIRule( + name="identity:list_domains_for_user", + check_str=(""), + description="List all domains a user has access to via role assignments.", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v3/auth/domains"}], + ), + base.APIRule( + name="identity:create_user", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "token.domain.id:%(target.user.domain_id)s) " + ), + description="Create a user.", + scope_types=["system", "domain"], + operations=[{"method": "POST", "path": "/v3/users"}], + ), + base.APIRule( + name="identity:update_user", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "token.domain.id:%(target.user.domain_id)s) " + ), + description="Update a user, including administrative password resets.", + scope_types=["system", "domain"], + operations=[{"method": "PATCH", "path": "/v3/users/{user_id}"}], + ), + base.APIRule( + name="identity:delete_user", + check_str=( + "(role:admin and system_scope:all) or (role:admin and " + "token.domain.id:%(target.user.domain_id)s) " + ), + description="Delete a user.", + scope_types=["system", "domain"], + operations=[{"method": "DELETE", "path": "/v3/users/{user_id}"}], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/neutron.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/neutron.py new file mode 100644 index 0000000..ebf5714 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/neutron.py @@ -0,0 +1,2228 @@ +from . import base + +list_rules = ( + base.Rule( + name="context_is_admin", + check_str=("role:admin"), + description="Rule for cloud admin access", + ), + base.Rule( + name="owner", + check_str=("tenant_id:%(tenant_id)s"), + description="Rule for resource owner access", + ), + base.Rule( + name="admin_or_owner", + check_str=("rule:context_is_admin or rule:owner"), + description="Rule for admin or owner access", + ), + base.Rule( + name="context_is_advsvc", + check_str=("role:advsvc"), + description="Rule for advsvc role access", + ), + base.Rule( + name="admin_or_network_owner", + check_str=("rule:context_is_admin or tenant_id:%(network:tenant_id)s"), + description="Rule for admin or network owner access", + ), + base.Rule( + name="admin_owner_or_network_owner", + check_str=("rule:owner or rule:admin_or_network_owner"), + description="Rule for resource owner, admin or network owner access", + ), + base.Rule( + name="network_owner", + check_str=("tenant_id:%(network:tenant_id)s"), + description="Rule for network owner access", + ), + base.Rule( + name="admin_only", + check_str=("rule:context_is_admin"), + description="Rule for admin-only access", + ), + base.Rule( + name="regular_user", + check_str=(""), + description="Rule for regular user access", + ), + base.Rule( + name="shared", + check_str=("field:networks:shared=True"), + description="Rule of shared network", + ), + base.Rule( + name="default", + check_str=("rule:admin_or_owner"), + description="Default access rule", + ), + base.Rule( + name="admin_or_ext_parent_owner", + check_str=("rule:context_is_admin or tenant_id:%(ext_parent:tenant_id)s"), + description="Rule for common parent owner check", + ), + base.Rule( + name="ext_parent_owner", + check_str=("tenant_id:%(ext_parent:tenant_id)s"), + description="Rule for common parent owner check", + ), + base.Rule( + name="sg_owner", + check_str=("tenant_id:%(security_group:tenant_id)s"), + description="Rule for security group owner access", + ), + base.Rule( + name="shared_address_groups", + check_str=("field:address_groups:shared=True"), + description="Definition of a shared address group", + ), + base.Rule( + name="shared_address_scopes", + check_str=("field:address_scopes:shared=True"), + description="Definition of a shared address scope", + ), + base.Rule( + name="get_flavor_service_profile", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a flavor associated with a given service profiles. " + "There is no corresponding GET operations in API " + "currently. This rule is currently referred only in the " + "DELETE of flavor_service_profile.", + ), + base.Rule( + name="external", + check_str=("field:networks:router:external=True"), + description="Definition of an external network", + ), + base.Rule( + name="network_device", + check_str=("field:port:device_owner=~^network:"), + description="Definition of port with network device_owner", + ), + base.Rule( + name="admin_or_data_plane_int", + check_str=("rule:context_is_admin or role:data_plane_integrator"), + description="Rule for data plane integration", + ), + base.Rule( + name="restrict_wildcard", + check_str=("(not field:rbac_policy:target_tenant=*) or rule:admin_only"), + description="Definition of a wildcard target_tenant", + ), + base.Rule( + name="admin_or_sg_owner", + check_str=("rule:context_is_admin or tenant_id:%(security_group:tenant_id)s"), + description="Rule for admin or security group owner access", + ), + base.Rule( + name="admin_owner_or_sg_owner", + check_str=("rule:owner or rule:admin_or_sg_owner"), + description="Rule for resource owner, admin or security group owner access", + ), + base.Rule( + name="shared_subnetpools", + check_str=("field:subnetpools:shared=True"), + description="Definition of a shared subnetpool", + ), + base.APIRule( + name="get_address_group", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or rule:shared_address_groups " + ), + description="Get an address group", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/address-groups"}, + {"method": "GET", "path": "/address-groups/{id}"}, + ], + ), + base.APIRule( + name="create_address_scope", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create an address scope", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/address-scopes"}], + ), + base.APIRule( + name="create_address_scope:shared", + check_str=("role:admin and system_scope:all"), + description="Create a shared address scope", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/address-scopes"}], + ), + base.APIRule( + name="get_address_scope", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or rule:shared_address_scopes " + ), + description="Get an address scope", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/address-scopes"}, + {"method": "GET", "path": "/address-scopes/{id}"}, + ], + ), + base.APIRule( + name="update_address_scope", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Update an address scope", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/address-scopes/{id}"}], + ), + base.APIRule( + name="update_address_scope:shared", + check_str=("role:admin and system_scope:all"), + description="Update ``shared`` attribute of an address scope", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/address-scopes/{id}"}], + ), + base.APIRule( + name="delete_address_scope", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete an address scope", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/address-scopes/{id}"}], + ), + base.APIRule( + name="get_agent", + check_str=("role:reader and system_scope:all"), + description="Get an agent", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/agents"}, + {"method": "GET", "path": "/agents/{id}"}, + ], + ), + base.APIRule( + name="update_agent", + check_str=("role:admin and system_scope:all"), + description="Update an agent", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/agents/{id}"}], + ), + base.APIRule( + name="delete_agent", + check_str=("role:admin and system_scope:all"), + description="Delete an agent", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/agents/{id}"}], + ), + base.APIRule( + name="create_dhcp-network", + check_str=("role:admin and system_scope:all"), + description="Add a network to a DHCP agent", + scope_types=["system"], + operations=[{"method": "POST", "path": "/agents/{agent_id}/dhcp-networks"}], + ), + base.APIRule( + name="get_dhcp-networks", + check_str=("role:reader and system_scope:all"), + description="List networks on a DHCP agent", + scope_types=["system"], + operations=[{"method": "GET", "path": "/agents/{agent_id}/dhcp-networks"}], + ), + base.APIRule( + name="delete_dhcp-network", + check_str=("role:admin and system_scope:all"), + description="Remove a network from a DHCP agent", + scope_types=["system"], + operations=[ + {"method": "DELETE", "path": "/agents/{agent_id}/dhcp-networks/{network_id}"}, + ], + ), + base.APIRule( + name="create_l3-router", + check_str=("role:admin and system_scope:all"), + description="Add a router to an L3 agent", + scope_types=["system"], + operations=[{"method": "POST", "path": "/agents/{agent_id}/l3-routers"}], + ), + base.APIRule( + name="get_l3-routers", + check_str=("role:reader and system_scope:all"), + description="List routers on an L3 agent", + scope_types=["system"], + operations=[{"method": "GET", "path": "/agents/{agent_id}/l3-routers"}], + ), + base.APIRule( + name="delete_l3-router", + check_str=("role:admin and system_scope:all"), + description="Remove a router from an L3 agent", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/agents/{agent_id}/l3-routers/{router_id}"}], + ), + base.APIRule( + name="get_dhcp-agents", + check_str=("role:reader and system_scope:all"), + description="List DHCP agents hosting a network", + scope_types=["system"], + operations=[{"method": "GET", "path": "/networks/{network_id}/dhcp-agents"}], + ), + base.APIRule( + name="get_l3-agents", + check_str=("role:reader and system_scope:all"), + description="List L3 agents hosting a router", + scope_types=["system"], + operations=[{"method": "GET", "path": "/routers/{router_id}/l3-agents"}], + ), + base.APIRule( + name="get_auto_allocated_topology", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a project's auto-allocated topology", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/auto-allocated-topology/{project_id}"}], + ), + base.APIRule( + name="delete_auto_allocated_topology", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete a project's auto-allocated topology", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/auto-allocated-topology/{project_id}"}], + ), + base.APIRule( + name="get_availability_zone", + check_str=("role:reader and system_scope:all"), + description="List availability zones", + scope_types=["system"], + operations=[{"method": "GET", "path": "/availability_zones"}], + ), + base.APIRule( + name="create_flavor", + check_str=("role:admin and system_scope:all"), + description="Create a flavor", + scope_types=["system"], + operations=[{"method": "POST", "path": "/flavors"}], + ), + base.APIRule( + name="get_flavor", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a flavor", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/flavors"}, + {"method": "GET", "path": "/flavors/{id}"}, + ], + ), + base.APIRule( + name="update_flavor", + check_str=("role:admin and system_scope:all"), + description="Update a flavor", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/flavors/{id}"}], + ), + base.APIRule( + name="delete_flavor", + check_str=("role:admin and system_scope:all"), + description="Delete a flavor", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/flavors/{id}"}], + ), + base.APIRule( + name="create_service_profile", + check_str=("role:admin and system_scope:all"), + description="Create a service profile", + scope_types=["system"], + operations=[{"method": "POST", "path": "/service_profiles"}], + ), + base.APIRule( + name="get_service_profile", + check_str=("role:reader and system_scope:all"), + description="Get a service profile", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/service_profiles"}, + {"method": "GET", "path": "/service_profiles/{id}"}, + ], + ), + base.APIRule( + name="update_service_profile", + check_str=("role:admin and system_scope:all"), + description="Update a service profile", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/service_profiles/{id}"}], + ), + base.APIRule( + name="delete_service_profile", + check_str=("role:admin and system_scope:all"), + description="Delete a service profile", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/service_profiles/{id}"}], + ), + base.APIRule( + name="create_flavor_service_profile", + check_str=("role:admin and system_scope:all"), + description="Associate a flavor with a service profile", + scope_types=["system"], + operations=[{"method": "POST", "path": "/flavors/{flavor_id}/service_profiles"}], + ), + base.APIRule( + name="delete_flavor_service_profile", + check_str=("role:admin and system_scope:all"), + description="Disassociate a flavor with a service profile", + scope_types=["system"], + operations=[ + {"method": "DELETE", "path": "/flavors/{flavor_id}/service_profiles/{profile_id}"}, + ], + ), + base.APIRule( + name="create_floatingip", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create a floating IP", + scope_types=["project"], + operations=[{"method": "POST", "path": "/floatingips"}], + ), + base.APIRule( + name="create_floatingip:floating_ip_address", + check_str=("role:admin and system_scope:all"), + description="Create a floating IP with a specific IP address", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/floatingips"}], + ), + base.APIRule( + name="get_floatingip", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a floating IP", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/floatingips"}, + {"method": "GET", "path": "/floatingips/{id}"}, + ], + ), + base.APIRule( + name="update_floatingip", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update a floating IP", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/floatingips/{id}"}], + ), + base.APIRule( + name="delete_floatingip", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Delete a floating IP", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/floatingips/{id}"}], + ), + base.APIRule( + name="get_floatingip_pool", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="Get floating IP pools", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/floatingip_pools"}], + ), + base.APIRule( + name="create_floatingip_port_forwarding", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Create a floating IP port forwarding", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/floatingips/{floatingip_id}/port_forwardings"}], + ), + base.APIRule( + name="get_floatingip_port_forwarding", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Get a floating IP port forwarding", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/floatingips/{floatingip_id}/port_forwardings"}, + { + "method": "GET", + "path": "/floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}", + }, + ], + ), + base.APIRule( + name="update_floatingip_port_forwarding", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Update a floating IP port forwarding", + scope_types=["system", "project"], + operations=[ + { + "method": "PUT", + "path": "/floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}", + }, + ], + ), + base.APIRule( + name="delete_floatingip_port_forwarding", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Delete a floating IP port forwarding", + scope_types=["system", "project"], + operations=[ + { + "method": "DELETE", + "path": "/floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}", + }, + ], + ), + base.APIRule( + name="create_router_conntrack_helper", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Create a router conntrack helper", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/routers/{router_id}/conntrack_helpers"}], + ), + base.APIRule( + name="get_router_conntrack_helper", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Get a router conntrack helper", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/routers/{router_id}/conntrack_helpers"}, + { + "method": "GET", + "path": "/routers/{router_id}/conntrack_helpers/{conntrack_helper_id}", + }, + ], + ), + base.APIRule( + name="update_router_conntrack_helper", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Update a router conntrack helper", + scope_types=["system", "project"], + operations=[ + { + "method": "PUT", + "path": "/routers/{router_id}/conntrack_helpers/{conntrack_helper_id}", + }, + ], + ), + base.APIRule( + name="delete_router_conntrack_helper", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:ext_parent_owner " + ), + description="Delete a router conntrack helper", + scope_types=["system", "project"], + operations=[ + { + "method": "DELETE", + "path": "/routers/{router_id}/conntrack_helpers/{conntrack_helper_id}", + }, + ], + ), + base.APIRule( + name="get_loggable_resource", + check_str=("role:reader and system_scope:all"), + description="Get loggable resources", + scope_types=["system"], + operations=[{"method": "GET", "path": "/log/loggable-resources"}], + ), + base.APIRule( + name="create_log", + check_str=("role:admin and system_scope:all"), + description="Create a network log", + scope_types=["system"], + operations=[{"method": "POST", "path": "/log/logs"}], + ), + base.APIRule( + name="get_log", + check_str=("role:reader and system_scope:all"), + description="Get a network log", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/log/logs"}, + {"method": "GET", "path": "/log/logs/{id}"}, + ], + ), + base.APIRule( + name="update_log", + check_str=("role:admin and system_scope:all"), + description="Update a network log", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/log/logs/{id}"}], + ), + base.APIRule( + name="delete_log", + check_str=("role:admin and system_scope:all"), + description="Delete a network log", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/log/logs/{id}"}], + ), + base.APIRule( + name="create_metering_label", + check_str=("role:admin and system_scope:all"), + description="Create a metering label", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/metering/metering-labels"}], + ), + base.APIRule( + name="get_metering_label", + check_str=("role:reader and system_scope:all"), + description="Get a metering label", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/metering/metering-labels"}, + {"method": "GET", "path": "/metering/metering-labels/{id}"}, + ], + ), + base.APIRule( + name="delete_metering_label", + check_str=("role:admin and system_scope:all"), + description="Delete a metering label", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/metering/metering-labels/{id}"}], + ), + base.APIRule( + name="create_metering_label_rule", + check_str=("role:admin and system_scope:all"), + description="Create a metering label rule", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/metering/metering-label-rules"}], + ), + base.APIRule( + name="get_metering_label_rule", + check_str=("role:reader and system_scope:all"), + description="Get a metering label rule", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/metering/metering-label-rules"}, + {"method": "GET", "path": "/metering/metering-label-rules/{id}"}, + ], + ), + base.APIRule( + name="delete_metering_label_rule", + check_str=("role:admin and system_scope:all"), + description="Delete a metering label rule", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/metering/metering-label-rules/{id}"}], + ), + base.APIRule( + name="create_network", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create a network", + scope_types=["project"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:shared", + check_str=("role:admin and system_scope:all"), + description="Create a shared network", + scope_types=["system"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:router:external", + check_str=("role:admin and system_scope:all"), + description="Create an external network", + scope_types=["system"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:is_default", + check_str=("role:admin and system_scope:all"), + description="Specify ``is_default`` attribute when creating a network", + scope_types=["system"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:port_security_enabled", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Specify ``port_security_enabled`` attribute when creating a network", + scope_types=["project"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:segments", + check_str=("role:admin and system_scope:all"), + description="Specify ``segments`` attribute when creating a network", + scope_types=["system"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:provider:network_type", + check_str=("role:admin and system_scope:all"), + description="Specify ``provider:network_type`` when creating a network", + scope_types=["system"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:provider:physical_network", + check_str=("role:admin and system_scope:all"), + description="Specify ``provider:physical_network`` when creating a network", + scope_types=["system"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="create_network:provider:segmentation_id", + check_str=("role:admin and system_scope:all"), + description="Specify ``provider:segmentation_id`` when creating a network", + scope_types=["system"], + operations=[{"method": "POST", "path": "/networks"}], + ), + base.APIRule( + name="get_network", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or rule:shared or rule:external or " + "rule:context_is_advsvc " + ), + description="Get a network", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/networks"}, + {"method": "GET", "path": "/networks/{id}"}, + ], + ), + base.APIRule( + name="get_network:router:external", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get ``router:external`` attribute of a network", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/networks"}, + {"method": "GET", "path": "/networks/{id}"}, + ], + ), + base.APIRule( + name="get_network:segments", + check_str=("role:reader and system_scope:all"), + description="Get ``segments`` attribute of a network", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/networks"}, + {"method": "GET", "path": "/networks/{id}"}, + ], + ), + base.APIRule( + name="get_network:provider:network_type", + check_str=("role:reader and system_scope:all"), + description="Get ``provider:network_type`` attribute of a network", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/networks"}, + {"method": "GET", "path": "/networks/{id}"}, + ], + ), + base.APIRule( + name="get_network:provider:physical_network", + check_str=("role:reader and system_scope:all"), + description="Get ``provider:physical_network`` attribute of a network", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/networks"}, + {"method": "GET", "path": "/networks/{id}"}, + ], + ), + base.APIRule( + name="get_network:provider:segmentation_id", + check_str=("role:reader and system_scope:all"), + description="Get ``provider:segmentation_id`` attribute of a network", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/networks"}, + {"method": "GET", "path": "/networks/{id}"}, + ], + ), + base.APIRule( + name="update_network", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update a network", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:segments", + check_str=("role:admin and system_scope:all"), + description="Update ``segments`` attribute of a network", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:shared", + check_str=("role:admin and system_scope:all"), + description="Update ``shared`` attribute of a network", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:provider:network_type", + check_str=("role:admin and system_scope:all"), + description="Update ``provider:network_type`` attribute of a network", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:provider:physical_network", + check_str=("role:admin and system_scope:all"), + description="Update ``provider:physical_network`` attribute of a network", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:provider:segmentation_id", + check_str=("role:admin and system_scope:all"), + description="Update ``provider:segmentation_id`` attribute of a network", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:router:external", + check_str=("role:admin and system_scope:all"), + description="Update ``router:external`` attribute of a network", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:is_default", + check_str=("role:admin and system_scope:all"), + description="Update ``is_default`` attribute of a network", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="update_network:port_security_enabled", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update ``port_security_enabled`` attribute of a network", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/networks/{id}"}], + ), + base.APIRule( + name="delete_network", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete a network", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/networks/{id}"}], + ), + base.APIRule( + name="get_network_ip_availability", + check_str=("role:reader and system_scope:all"), + description="Get network IP availability", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/network-ip-availabilities"}, + {"method": "GET", "path": "/network-ip-availabilities/{network_id}"}, + ], + ), + base.APIRule( + name="create_network_segment_range", + check_str=("role:admin and system_scope:all"), + description="Create a network segment range", + scope_types=["system"], + operations=[{"method": "POST", "path": "/network_segment_ranges"}], + ), + base.APIRule( + name="get_network_segment_range", + check_str=("role:reader and system_scope:all"), + description="Get a network segment range", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/network_segment_ranges"}, + {"method": "GET", "path": "/network_segment_ranges/{id}"}, + ], + ), + base.APIRule( + name="update_network_segment_range", + check_str=("role:admin and system_scope:all"), + description="Update a network segment range", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/network_segment_ranges/{id}"}], + ), + base.APIRule( + name="delete_network_segment_range", + check_str=("role:admin and system_scope:all"), + description="Delete a network segment range", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/network_segment_ranges/{id}"}], + ), + base.APIRule( + name="create_port", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create a port", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:device_owner", + check_str=( + "not rule:network_device or role:admin and system_scope:all or " + "role:admin and project_id:%(project_id)s or " + "rule:context_is_advsvc or rule:network_owner " + ), + description="Specify ``device_owner`` attribute when creting a port", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:mac_address", + check_str=( + "rule:context_is_advsvc or rule:network_owner " + "or role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s" + ), + description="Specify ``mac_address`` attribute when creating a port", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:fixed_ips", + check_str=( + "rule:context_is_advsvc or rule:network_owner " + "or role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s or rule:shared" + ), + description="Specify ``fixed_ips`` information when creating a port", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:fixed_ips:ip_address", + check_str=( + "rule:context_is_advsvc or rule:network_owner " + "or role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s" + ), + description="Specify IP address in ``fixed_ips`` when creating a port", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:fixed_ips:subnet_id", + check_str=( + "rule:context_is_advsvc or rule:network_owner or " + "role:admin and system_scope:all or role:admin and " + "project_id:%(project_id)s or rule:shared" + ), + description="Specify subnet ID in ``fixed_ips`` when creating a port", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:port_security_enabled", + check_str=( + "rule:context_is_advsvc or rule:network_owner " + "or role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s" + ), + description="Specify ``port_security_enabled`` attribute when creating a port", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:binding:host_id", + check_str=("role:admin and system_scope:all"), + description="Specify ``binding:host_id`` attribute when creating a port", + scope_types=["system"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:binding:profile", + check_str=("role:admin and system_scope:all"), + description="Specify ``binding:profile`` attribute when creating a port", + scope_types=["system"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:binding:vnic_type", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Specify ``binding:vnic_type`` attribute when creating a port", + scope_types=["project"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:allowed_address_pairs", + check_str=( + "role:admin and system_scope:all or role:admin and project_id:%(" + "project_id)s or rule:network_owner " + ), + description="Specify ``allowed_address_pairs`` attribute when creating a port", + scope_types=["project", "system"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:allowed_address_pairs:mac_address", + check_str=( + "role:admin and system_scope:all or role:admin and project_id:%(" + "project_id)s or rule:network_owner " + ), + description="Specify ``mac_address` of `allowed_address_pairs`` " + "attribute when creating a port", + scope_types=["project", "system"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="create_port:allowed_address_pairs:ip_address", + check_str=( + "role:admin and system_scope:all or role:admin and project_id:%(" + "project_id)s or rule:network_owner " + ), + description="Specify ``ip_address`` of ``allowed_address_pairs`` " + "attribute when creating a port", + scope_types=["project", "system"], + operations=[{"method": "POST", "path": "/ports"}], + ), + base.APIRule( + name="get_port", + check_str=( + "rule:context_is_advsvc or (role:reader and system_scope:all) or " + "(role:reader and project_id:%(project_id)s) " + ), + description="Get a port", + scope_types=["project", "system"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/{id}"}, + ], + ), + base.APIRule( + name="get_port:binding:vif_type", + check_str=("role:reader and system_scope:all"), + description="Get ``binding:vif_type`` attribute of a port", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/{id}"}, + ], + ), + base.APIRule( + name="get_port:binding:vif_details", + check_str=("role:reader and system_scope:all"), + description="Get ``binding:vif_details`` attribute of a port", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/{id}"}, + ], + ), + base.APIRule( + name="get_port:binding:host_id", + check_str=("role:reader and system_scope:all"), + description="Get ``binding:host_id`` attribute of a port", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/{id}"}, + ], + ), + base.APIRule( + name="get_port:binding:profile", + check_str=("role:reader and system_scope:all"), + description="Get ``binding:profile`` attribute of a port", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/{id}"}, + ], + ), + base.APIRule( + name="get_port:resource_request", + check_str=("role:reader and system_scope:all"), + description="Get ``resource_request`` attribute of a port", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/ports"}, + {"method": "GET", "path": "/ports/{id}"}, + ], + ), + base.APIRule( + name="update_port", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:context_is_advsvc " + ), + description="Update a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:device_owner", + check_str=( + "not rule:network_device or rule:context_is_advsvc " + "or rule:network_owner or role:admin and system_scope:all " + "or role:admin and project_id:%(project_id)s" + ), + description="Update ``device_owner`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:mac_address", + check_str=("role:admin and system_scope:all or rule:context_is_advsvc"), + description="Update ``mac_address`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:fixed_ips", + check_str=( + "rule:context_is_advsvc or rule:network_owner or " + "role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s" + ), + description="Specify ``fixed_ips`` information when updating a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:fixed_ips:ip_address", + check_str=( + "rule:context_is_advsvc or rule:network_owner " + "or role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s" + ), + description="Specify IP address in ``fixed_ips`` information when updating a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:fixed_ips:subnet_id", + check_str=( + "rule:context_is_advsvc or rule:network_owner " + "or role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s or rule:shared" + ), + description="Specify subnet ID in ``fixed_ips`` information when updating a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:port_security_enabled", + check_str=( + "rule:context_is_advsvc or rule:network_owner " + "or role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s" + ), + description="Update ``port_security_enabled`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:binding:host_id", + check_str=("role:admin and system_scope:all"), + description="Update ``binding:host_id`` attribute of a port", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:binding:profile", + check_str=("role:admin and system_scope:all"), + description="Update ``binding:profile`` attribute of a port", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:binding:vnic_type", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:context_is_advsvc" + ), + description="Update ``binding:vnic_type`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:allowed_address_pairs", + check_str=( + "role:admin and system_scope:all or role:admin and " + "project_id:%(project_id)s or rule:network_owner" + ), + description="Update ``allowed_address_pairs`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:allowed_address_pairs:mac_address", + check_str=( + "role:admin and system_scope:all or role:admin " + "and project_id:%(project_id)s or rule:network_owner" + ), + description="Update ``mac_address`` of ``allowed_address_pairs`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:allowed_address_pairs:ip_address", + check_str=( + "role:admin and system_scope:all or role:admin and " + "project_id:%(project_id)s or rule:network_owner" + ), + description="Update ``ip_address`` of ``allowed_address_pairs`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="update_port:data_plane_status", + check_str=("role:admin and system_scope:all or role:data_plane_integrator"), + description="Update ``data_plane_status`` attribute of a port", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/ports/{id}"}], + ), + base.APIRule( + name="delete_port", + check_str=( + "rule:context_is_advsvc or (role:admin and system_scope:all) " + "or (role:member and project_id:%(project_id)s)" + ), + description="Delete a port", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/ports/{id}"}], + ), + base.APIRule( + name="get_policy", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get QoS policies", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/qos/policies"}, + {"method": "GET", "path": "/qos/policies/{id}"}, + ], + ), + base.APIRule( + name="create_policy", + check_str=("role:admin and system_scope:all"), + description="Create a QoS policy", + scope_types=["system"], + operations=[{"method": "POST", "path": "/qos/policies"}], + ), + base.APIRule( + name="update_policy", + check_str=("role:admin and system_scope:all"), + description="Update a QoS policy", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/qos/policies/{id}"}], + ), + base.APIRule( + name="delete_policy", + check_str=("role:admin and system_scope:all"), + description="Delete a QoS policy", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/qos/policies/{id}"}], + ), + base.APIRule( + name="get_rule_type", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get available QoS rule types", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/qos/rule-types"}, + {"method": "GET", "path": "/qos/rule-types/{rule_type}"}, + ], + ), + base.APIRule( + name="get_policy_bandwidth_limit_rule", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a QoS bandwidth limit rule", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/qos/policies/{policy_id}/bandwidth_limit_rules"}, + { + "method": "GET", + "path": "/qos/policies/{policy_id}/bandwidth_limit_rules/{rule_id}", + }, + ], + ), + base.APIRule( + name="create_policy_bandwidth_limit_rule", + check_str=("role:admin and system_scope:all"), + description="Create a QoS bandwidth limit rule", + scope_types=["system"], + operations=[ + {"method": "POST", "path": "/qos/policies/{policy_id}/bandwidth_limit_rules"}, + ], + ), + base.APIRule( + name="update_policy_bandwidth_limit_rule", + check_str=("role:admin and system_scope:all"), + description="Update a QoS bandwidth limit rule", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/qos/policies/{policy_id}/bandwidth_limit_rules/{rule_id}", + }, + ], + ), + base.APIRule( + name="delete_policy_bandwidth_limit_rule", + check_str=("role:admin and system_scope:all"), + description="Delete a QoS bandwidth limit rule", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/qos/policies/{policy_id}/bandwidth_limit_rules/{rule_id}", + }, + ], + ), + base.APIRule( + name="get_policy_dscp_marking_rule", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a QoS DSCP marking rule", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/qos/policies/{policy_id}/dscp_marking_rules"}, + {"method": "GET", "path": "/qos/policies/{policy_id}/dscp_marking_rules/{rule_id}"}, + ], + ), + base.APIRule( + name="create_policy_dscp_marking_rule", + check_str=("role:admin and system_scope:all"), + description="Create a QoS DSCP marking rule", + scope_types=["system"], + operations=[{"method": "POST", "path": "/qos/policies/{policy_id}/dscp_marking_rules"}], + ), + base.APIRule( + name="update_policy_dscp_marking_rule", + check_str=("role:admin and system_scope:all"), + description="Update a QoS DSCP marking rule", + scope_types=["system"], + operations=[ + {"method": "PUT", "path": "/qos/policies/{policy_id}/dscp_marking_rules/{rule_id}"}, + ], + ), + base.APIRule( + name="delete_policy_dscp_marking_rule", + check_str=("role:admin and system_scope:all"), + description="Delete a QoS DSCP marking rule", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/qos/policies/{policy_id}/dscp_marking_rules/{rule_id}", + }, + ], + ), + base.APIRule( + name="get_policy_minimum_bandwidth_rule", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a QoS minimum bandwidth rule", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/qos/policies/{policy_id}/minimum_bandwidth_rules"}, + { + "method": "GET", + "path": "/qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}", + }, + ], + ), + base.APIRule( + name="create_policy_minimum_bandwidth_rule", + check_str=("role:admin and system_scope:all"), + description="Create a QoS minimum bandwidth rule", + scope_types=["system"], + operations=[ + {"method": "POST", "path": "/qos/policies/{policy_id}/minimum_bandwidth_rules"}, + ], + ), + base.APIRule( + name="update_policy_minimum_bandwidth_rule", + check_str=("role:admin and system_scope:all"), + description="Update a QoS minimum bandwidth rule", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}", + }, + ], + ), + base.APIRule( + name="delete_policy_minimum_bandwidth_rule", + check_str=("role:admin and system_scope:all"), + description="Delete a QoS minimum bandwidth rule", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}", + }, + ], + ), + base.APIRule( + name="get_alias_bandwidth_limit_rule", + check_str=("rule:get_policy_bandwidth_limit_rule"), + description="Get a QoS bandwidth limit rule through alias", + scope_types=["project"], + operations=[{"method": "GET", "path": "/qos/alias_bandwidth_limit_rules/{rule_id}/"}], + ), + base.APIRule( + name="update_alias_bandwidth_limit_rule", + check_str=("rule:update_policy_bandwidth_limit_rule"), + description="Update a QoS bandwidth limit rule through alias", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/qos/alias_bandwidth_limit_rules/{rule_id}/"}], + ), + base.APIRule( + name="delete_alias_bandwidth_limit_rule", + check_str=("rule:delete_policy_bandwidth_limit_rule"), + description="Delete a QoS bandwidth limit rule through alias", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/qos/alias_bandwidth_limit_rules/{rule_id}/"}], + ), + base.APIRule( + name="get_alias_dscp_marking_rule", + check_str=("rule:get_policy_dscp_marking_rule"), + description="Get a QoS DSCP marking rule through alias", + scope_types=["project"], + operations=[{"method": "GET", "path": "/qos/alias_dscp_marking_rules/{rule_id}/"}], + ), + base.APIRule( + name="update_alias_dscp_marking_rule", + check_str=("rule:update_policy_dscp_marking_rule"), + description="Update a QoS DSCP marking rule through alias", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/qos/alias_dscp_marking_rules/{rule_id}/"}], + ), + base.APIRule( + name="delete_alias_dscp_marking_rule", + check_str=("rule:delete_policy_dscp_marking_rule"), + description="Delete a QoS DSCP marking rule through alias", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/qos/alias_dscp_marking_rules/{rule_id}/"}], + ), + base.APIRule( + name="get_alias_minimum_bandwidth_rule", + check_str=("rule:get_policy_minimum_bandwidth_rule"), + description="Get a QoS minimum bandwidth rule through alias", + scope_types=["project"], + operations=[{"method": "GET", "path": "/qos/alias_minimum_bandwidth_rules/{rule_id}/"}], + ), + base.APIRule( + name="update_alias_minimum_bandwidth_rule", + check_str=("rule:update_policy_minimum_bandwidth_rule"), + description="Update a QoS minimum bandwidth rule through alias", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/qos/alias_minimum_bandwidth_rules/{rule_id}/"}], + ), + base.APIRule( + name="delete_alias_minimum_bandwidth_rule", + check_str=("rule:delete_policy_minimum_bandwidth_rule"), + description="Delete a QoS minimum bandwidth rule through alias", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/qos/alias_minimum_bandwidth_rules/{rule_id}/"}, + ], + ), + base.APIRule( + name="get_quota", + check_str=("role:reader and system_scope:all"), + description="Get a resource quota", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/quota"}, + {"method": "GET", "path": "/quota/{id}"}, + ], + ), + base.APIRule( + name="update_quota", + check_str=("role:admin and system_scope:all"), + description="Update a resource quota", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/quota/{id}"}], + ), + base.APIRule( + name="delete_quota", + check_str=("role:admin and system_scope:all"), + description="Delete a resource quota", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/quota/{id}"}], + ), + base.APIRule( + name="create_rbac_policy", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create an RBAC policy", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/rbac-policies"}], + ), + base.APIRule( + name="create_rbac_policy:target_tenant", + check_str=("role:admin and system_scope:all or rule:restrict_wildcard"), + description="Specify ``target_tenant`` when creating an RBAC policy", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/rbac-policies"}], + ), + base.APIRule( + name="update_rbac_policy", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update an RBAC policy", + scope_types=["project", "system"], + operations=[{"method": "PUT", "path": "/rbac-policies/{id}"}], + ), + base.APIRule( + name="update_rbac_policy:target_tenant", + check_str=("role:admin and system_scope:all or rule:restrict_wildcard"), + description="Update ``target_tenant`` attribute of an RBAC policy", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/rbac-policies/{id}"}], + ), + base.APIRule( + name="get_rbac_policy", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get an RBAC policy", + scope_types=["project", "system"], + operations=[ + {"method": "GET", "path": "/rbac-policies"}, + {"method": "GET", "path": "/rbac-policies/{id}"}, + ], + ), + base.APIRule( + name="delete_rbac_policy", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete an RBAC policy", + scope_types=["project", "system"], + operations=[{"method": "DELETE", "path": "/rbac-policies/{id}"}], + ), + base.APIRule( + name="create_router", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create a router", + scope_types=["project"], + operations=[{"method": "POST", "path": "/routers"}], + ), + base.APIRule( + name="create_router:distributed", + check_str=("role:admin and system_scope:all"), + description="Specify ``distributed`` attribute when creating a router", + scope_types=["system"], + operations=[{"method": "POST", "path": "/routers"}], + ), + base.APIRule( + name="create_router:ha", + check_str=("role:admin and system_scope:all"), + description="Specify ``ha`` attribute when creating a router", + scope_types=["system"], + operations=[{"method": "POST", "path": "/routers"}], + ), + base.APIRule( + name="create_router:external_gateway_info", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Specify ``external_gateway_info`` information when creating a router", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/routers"}], + ), + base.APIRule( + name="create_router:external_gateway_info:network_id", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Specify ``network_id`` in ``external_gateway_info`` " + "information when creating a router", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/routers"}], + ), + base.APIRule( + name="create_router:external_gateway_info:enable_snat", + check_str=("role:admin and system_scope:all"), + description="Specify ``enable_snat`` " + "in ``external_gateway_info`` information " + "when creating a router", + scope_types=["system"], + operations=[{"method": "POST", "path": "/routers"}], + ), + base.APIRule( + name="create_router:external_gateway_info:external_fixed_ips", + check_str=("role:admin and system_scope:all"), + description="Specify ``external_fixed_ips`` " + "in ``external_gateway_info`` information " + "when creating a router", + scope_types=["system"], + operations=[{"method": "POST", "path": "/routers"}], + ), + base.APIRule( + name="get_router", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a router", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/routers"}, + {"method": "GET", "path": "/routers/{id}"}, + ], + ), + base.APIRule( + name="get_router:distributed", + check_str=("role:reader and system_scope:all"), + description="Get ``distributed`` attribute of a router", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/routers"}, + {"method": "GET", "path": "/routers/{id}"}, + ], + ), + base.APIRule( + name="get_router:ha", + check_str=("role:reader and system_scope:all"), + description="Get ``ha`` attribute of a router", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/routers"}, + {"method": "GET", "path": "/routers/{id}"}, + ], + ), + base.APIRule( + name="update_router", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update a router", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/routers/{id}"}], + ), + base.APIRule( + name="update_router:distributed", + check_str=("role:admin and system_scope:all"), + description="Update ``distributed`` attribute of a router", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/routers/{id}"}], + ), + base.APIRule( + name="update_router:ha", + check_str=("role:admin and system_scope:all"), + description="Update ``ha`` attribute of a router", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/routers/{id}"}], + ), + base.APIRule( + name="update_router:external_gateway_info", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update ``external_gateway_info`` information of a router", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/routers/{id}"}], + ), + base.APIRule( + name="update_router:external_gateway_info:network_id", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update ``network_id`` attribute " + "of ``external_gateway_info`` information of a router", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/routers/{id}"}], + ), + base.APIRule( + name="update_router:external_gateway_info:enable_snat", + check_str=("role:admin and system_scope:all"), + description="Update ``enable_snat`` attribute " + "of ``external_gateway_info`` information of a router", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/routers/{id}"}], + ), + base.APIRule( + name="update_router:external_gateway_info:external_fixed_ips", + check_str=("role:admin and system_scope:all"), + description="Update ``external_fixed_ips`` attribute " + "of ``external_gateway_info`` information of a router", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/routers/{id}"}], + ), + base.APIRule( + name="delete_router", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete a router", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/routers/{id}"}], + ), + base.APIRule( + name="add_router_interface", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Add an interface to a router", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/routers/{id}/add_router_interface"}], + ), + base.APIRule( + name="remove_router_interface", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Remove an interface from a router", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/routers/{id}/remove_router_interface"}], + ), + base.APIRule( + name="create_security_group", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create a security group", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/security-groups"}], + ), + base.APIRule( + name="get_security_group", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get a security group", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/security-groups"}, + {"method": "GET", "path": "/security-groups/{id}"}, + ], + ), + base.APIRule( + name="update_security_group", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Update a security group", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/security-groups/{id}"}], + ), + base.APIRule( + name="delete_security_group", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete a security group", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/security-groups/{id}"}], + ), + base.APIRule( + name="create_security_group_rule", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Create a security group rule", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/security-group-rules"}], + ), + base.APIRule( + name="get_security_group_rule", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and project_id:%(project_id)s) or rule:sg_owner" + ), + description="Get a security group rule", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/security-group-rules"}, + {"method": "GET", "path": "/security-group-rules/{id}"}, + ], + ), + base.APIRule( + name="delete_security_group_rule", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete a security group rule", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/security-group-rules/{id}"}], + ), + base.APIRule( + name="create_segment", + check_str=("role:admin and system_scope:all"), + description="Create a segment", + scope_types=["system"], + operations=[{"method": "POST", "path": "/segments"}], + ), + base.APIRule( + name="get_segment", + check_str=("role:reader and system_scope:all"), + description="Get a segment", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/segments"}, + {"method": "GET", "path": "/segments/{id}"}, + ], + ), + base.APIRule( + name="update_segment", + check_str=("role:admin and system_scope:all"), + description="Update a segment", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/segments/{id}"}], + ), + base.APIRule( + name="delete_segment", + check_str=("role:admin and system_scope:all"), + description="Delete a segment", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/segments/{id}"}], + ), + base.APIRule( + name="get_service_provider", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="Get service providers", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/service-providers"}], + ), + base.APIRule( + name="create_subnet", + check_str=( + "(role:admin and system_scope:all) " + "or (role:member and project_id:%(project_id)s) " + "or rule:network_owner" + ), + description="Create a subnet", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/subnets"}], + ), + base.APIRule( + name="create_subnet:segment_id", + check_str=("role:admin and system_scope:all"), + description="Specify ``segment_id`` attribute when creating a subnet", + scope_types=["system"], + operations=[{"method": "POST", "path": "/subnets"}], + ), + base.APIRule( + name="create_subnet:service_types", + check_str=("role:admin and system_scope:all"), + description="Specify ``service_types`` attribute when creating a subnet", + scope_types=["system"], + operations=[{"method": "POST", "path": "/subnets"}], + ), + base.APIRule( + name="get_subnet", + check_str=( + "(role:reader and system_scope:all) " + "or (role:reader and project_id:%(project_id)s) or rule:shared" + ), + description="Get a subnet", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/subnets"}, + {"method": "GET", "path": "/subnets/{id}"}, + ], + ), + base.APIRule( + name="get_subnet:segment_id", + check_str=("role:reader and system_scope:all"), + description="Get ``segment_id`` attribute of a subnet", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/subnets"}, + {"method": "GET", "path": "/subnets/{id}"}, + ], + ), + base.APIRule( + name="update_subnet", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:network_owner " + ), + description="Update a subnet", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/subnets/{id}"}], + ), + base.APIRule( + name="update_subnet:segment_id", + check_str=("role:admin and system_scope:all"), + description="Update ``segment_id`` attribute of a subnet", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/subnets/{id}"}], + ), + base.APIRule( + name="update_subnet:service_types", + check_str=("role:admin and system_scope:all"), + description="Update ``service_types`` attribute of a subnet", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/subnets/{id}"}], + ), + base.APIRule( + name="delete_subnet", + check_str=( + "(role:admin and system_scope:all) or (role:member and " + "project_id:%(project_id)s) or rule:network_owner " + ), + description="Delete a subnet", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/subnets/{id}"}], + ), + base.APIRule( + name="create_subnetpool", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Create a subnetpool", + scope_types=["project", "system"], + operations=[{"method": "POST", "path": "/subnetpools"}], + ), + base.APIRule( + name="create_subnetpool:shared", + check_str=("role:admin and system_scope:all"), + description="Create a shared subnetpool", + scope_types=["system"], + operations=[{"method": "POST", "path": "/subnetpools"}], + ), + base.APIRule( + name="create_subnetpool:is_default", + check_str=("role:admin and system_scope:all"), + description="Specify ``is_default`` attribute when creating a subnetpool", + scope_types=["system"], + operations=[{"method": "POST", "path": "/subnetpools"}], + ), + base.APIRule( + name="get_subnetpool", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) or rule:shared_subnetpools " + ), + description="Get a subnetpool", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/subnetpools"}, + {"method": "GET", "path": "/subnetpools/{id}"}, + ], + ), + base.APIRule( + name="update_subnetpool", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Update a subnetpool", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/subnetpools/{id}"}], + ), + base.APIRule( + name="update_subnetpool:is_default", + check_str=("role:admin and system_scope:all"), + description="Update ``is_default`` attribute of a subnetpool", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/subnetpools/{id}"}], + ), + base.APIRule( + name="delete_subnetpool", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Delete a subnetpool", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/subnetpools/{id}"}], + ), + base.APIRule( + name="onboard_network_subnets", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Onboard existing subnet into a subnetpool", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/subnetpools/{id}/onboard_network_subnets"}], + ), + base.APIRule( + name="add_prefixes", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Add prefixes to a subnetpool", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/subnetpools/{id}/add_prefixes"}], + ), + base.APIRule( + name="remove_prefixes", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Remove unallocated prefixes from a subnetpool", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/subnetpools/{id}/remove_prefixes"}], + ), + base.APIRule( + name="create_trunk", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Create a trunk", + scope_types=["project", "system"], + operations=[{"method": "POST", "path": "/trunks"}], + ), + base.APIRule( + name="get_trunk", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="Get a trunk", + scope_types=["project", "system"], + operations=[ + {"method": "GET", "path": "/trunks"}, + {"method": "GET", "path": "/trunks/{id}"}, + ], + ), + base.APIRule( + name="update_trunk", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Update a trunk", + scope_types=["project", "system"], + operations=[{"method": "PUT", "path": "/trunks/{id}"}], + ), + base.APIRule( + name="delete_trunk", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s) " + ), + description="Delete a trunk", + scope_types=["project", "system"], + operations=[{"method": "DELETE", "path": "/trunks/{id}"}], + ), + base.APIRule( + name="get_subports", + check_str=( + "(role:reader and system_scope:all) or (role:reader and " + "project_id:%(project_id)s) " + ), + description="List subports attached to a trunk", + scope_types=["project", "system"], + operations=[{"method": "GET", "path": "/trunks/{id}/get_subports"}], + ), + base.APIRule( + name="add_subports", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Add subports to a trunk", + scope_types=["project", "system"], + operations=[{"method": "PUT", "path": "/trunks/{id}/add_subports"}], + ), + base.APIRule( + name="remove_subports", + check_str=( + "(role:admin and system_scope:all) or (role:member and project_id:%(project_id)s)" + ), + description="Delete subports from a trunk", + scope_types=["project", "system"], + operations=[{"method": "PUT", "path": "/trunks/{id}/remove_subports"}], + ), + base.APIRule( + name="create_endpoint_group", + check_str=("rule:regular_user"), + description="Create a VPN endpoint group", + scope_types=["project"], + operations=[{"method": "POST", "path": "/vpn/endpoint-groups"}], + ), + base.APIRule( + name="update_endpoint_group", + check_str=("rule:admin_or_owner"), + description="Update a VPN endpoint group", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/vpn/endpoint-groups/{id}"}], + ), + base.APIRule( + name="delete_endpoint_group", + check_str=("rule:admin_or_owner"), + description="Delete a VPN endpoint group", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/vpn/endpoint-groups/{id}"}], + ), + base.APIRule( + name="get_endpoint_group", + check_str=("rule:admin_or_owner"), + description="Get VPN endpoint groups", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/vpn/endpoint-groups"}, + {"method": "GET", "path": "/vpn/endpoint-groups/{id}"}, + ], + ), + base.APIRule( + name="create_ikepolicy", + check_str=("rule:regular_user"), + description="Create an IKE policy", + scope_types=["project"], + operations=[{"method": "POST", "path": "/vpn/ikepolicies"}], + ), + base.APIRule( + name="update_ikepolicy", + check_str=("rule:admin_or_owner"), + description="Update an IKE policy", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/vpn/ikepolicies/{id}"}], + ), + base.APIRule( + name="delete_ikepolicy", + check_str=("rule:admin_or_owner"), + description="Delete an IKE policy", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/vpn/ikepolicies/{id}"}], + ), + base.APIRule( + name="get_ikepolicy", + check_str=("rule:admin_or_owner"), + description="Get IKE policyies", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/vpn/ikepolicies"}, + {"method": "GET", "path": "/vpn/ikepolicies/{id}"}, + ], + ), + base.APIRule( + name="create_ipsecpolicy", + check_str=("rule:regular_user"), + description="Create an IPsec policy", + scope_types=["project"], + operations=[{"method": "POST", "path": "/vpn/ipsecpolicies"}], + ), + base.APIRule( + name="update_ipsecpolicy", + check_str=("rule:admin_or_owner"), + description="Update an IPsec policy", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/vpn/ipsecpolicies/{id}"}], + ), + base.APIRule( + name="delete_ipsecpolicy", + check_str=("rule:admin_or_owner"), + description="Delete an IPsec policy", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/vpn/ipsecpolicies/{id}"}], + ), + base.APIRule( + name="get_ipsecpolicy", + check_str=("rule:admin_or_owner"), + description="Get IPsec policies", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/vpn/ipsecpolicies"}, + {"method": "GET", "path": "/vpn/ipsecpolicies/{id}"}, + ], + ), + base.APIRule( + name="create_ipsec_site_connection", + check_str=("rule:regular_user"), + description="Create an IPsec site connection", + scope_types=["project"], + operations=[{"method": "POST", "path": "/vpn/ipsec-site-connections"}], + ), + base.APIRule( + name="update_ipsec_site_connection", + check_str=("rule:admin_or_owner"), + description="Update an IPsec site connection", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/vpn/ipsec-site-connections/{id}"}], + ), + base.APIRule( + name="delete_ipsec_site_connection", + check_str=("rule:admin_or_owner"), + description="Delete an IPsec site connection", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/vpn/ipsec-site-connections/{id}"}], + ), + base.APIRule( + name="get_ipsec_site_connection", + check_str=("rule:admin_or_owner"), + description="Get IPsec site connections", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/vpn/ipsec-site-connections"}, + {"method": "GET", "path": "/vpn/ipsec-site-connections/{id}"}, + ], + ), + base.APIRule( + name="create_vpnservice", + check_str=("rule:regular_user"), + description="Create a VPN service", + scope_types=["project"], + operations=[{"method": "POST", "path": "/vpn/vpnservices"}], + ), + base.APIRule( + name="update_vpnservice", + check_str=("rule:admin_or_owner"), + description="Update a VPN service", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/vpn/vpnservices/{id}"}], + ), + base.APIRule( + name="delete_vpnservice", + check_str=("rule:admin_or_owner"), + description="Delete a VPN service", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/vpn/vpnservices/{id}"}], + ), + base.APIRule( + name="get_vpnservice", + check_str=("rule:admin_or_owner"), + description="Get VPN services", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/vpn/vpnservices"}, + {"method": "GET", "path": "/vpn/vpnservices/{id}"}, + ], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/nova.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/nova.py new file mode 100644 index 0000000..e691dd2 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/nova.py @@ -0,0 +1,1647 @@ +from . import base + +list_rules = ( + base.Rule( + name="context_is_admin", + check_str=("role:admin"), + description="Decides what is required for the 'is_admin:True' check to succeed.", + ), + base.Rule( + name="admin_or_owner", + check_str=("is_admin:True or project_id:%(project_id)s"), + description="Default rule for most non-Admin APIs.", + ), + base.Rule( + name="admin_api", + check_str=("is_admin:True"), + description="Default rule for most Admin APIs.", + ), + base.Rule( + name="system_admin_api", + check_str=("role:admin and system_scope:all"), + description="Default rule for System Admin APIs.", + ), + base.Rule( + name="system_reader_api", + check_str=("role:reader and system_scope:all"), + description="Default rule for System level read only APIs.", + ), + base.Rule( + name="project_admin_api", + check_str=("role:admin and project_id:%(project_id)s"), + description="Default rule for Project level admin APIs.", + ), + base.Rule( + name="project_member_api", + check_str=("role:member and project_id:%(project_id)s"), + description="Default rule for Project level non admin APIs.", + ), + base.Rule( + name="project_reader_api", + check_str=("role:reader and project_id:%(project_id)s"), + description="Default rule for Project level read only APIs.", + ), + base.Rule( + name="system_admin_or_owner", + check_str=("rule:system_admin_api or rule:project_member_api"), + description="Default rule for System admin+owner APIs.", + ), + base.Rule( + name="system_or_project_reader", + check_str=("rule:system_reader_api or rule:project_reader_api"), + description="Default rule for System+Project read only APIs.", + ), + base.APIRule( + name="os_compute_api:os-admin-actions:reset_state", + check_str=("rule:system_admin_api"), + description="Reset the state of a given server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (os-resetState)"}], + ), + base.APIRule( + name="os_compute_api:os-admin-actions:inject_network_info", + check_str=("rule:system_admin_api"), + description="Inject network information into the server", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers/{server_id}/action (injectNetworkInfo)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-admin-password", + check_str=("rule:system_admin_or_owner"), + description="Change the administrative password for a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (changePassword)"}], + ), + base.APIRule( + name="os_compute_api:os-aggregates:set_metadata", + check_str=("rule:system_admin_api"), + description="Create or replace metadata for an aggregate", + scope_types=["system"], + operations=[ + {"method": "POST", "path": "/os-aggregates/{aggregate_id}/action (set_metadata)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-aggregates:add_host", + check_str=("rule:system_admin_api"), + description="Add a host to an aggregate", + scope_types=["system"], + operations=[ + {"method": "POST", "path": "/os-aggregates/{aggregate_id}/action (add_host)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-aggregates:create", + check_str=("rule:system_admin_api"), + description="Create an aggregate", + scope_types=["system"], + operations=[{"method": "POST", "path": "/os-aggregates"}], + ), + base.APIRule( + name="os_compute_api:os-aggregates:remove_host", + check_str=("rule:system_admin_api"), + description="Remove a host from an aggregate", + scope_types=["system"], + operations=[ + {"method": "POST", "path": "/os-aggregates/{aggregate_id}/action (remove_host)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-aggregates:update", + check_str=("rule:system_admin_api"), + description="Update name and/or availability zone for an aggregate", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/os-aggregates/{aggregate_id}"}], + ), + base.APIRule( + name="os_compute_api:os-aggregates:index", + check_str=("rule:system_reader_api"), + description="List all aggregates", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-aggregates"}], + ), + base.APIRule( + name="os_compute_api:os-aggregates:delete", + check_str=("rule:system_admin_api"), + description="Delete an aggregate", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/os-aggregates/{aggregate_id}"}], + ), + base.APIRule( + name="os_compute_api:os-aggregates:show", + check_str=("rule:system_reader_api"), + description="Show details for an aggregate", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-aggregates/{aggregate_id}"}], + ), + base.APIRule( + name="compute:aggregates:images", + check_str=("rule:system_admin_api"), + description="Request image caching for an aggregate", + scope_types=["system"], + operations=[{"method": "POST", "path": "/os-aggregates/{aggregate_id}/images"}], + ), + base.APIRule( + name="os_compute_api:os-assisted-volume-snapshots:create", + check_str=("rule:system_admin_api"), + description="Create an assisted volume snapshot", + scope_types=["system"], + operations=[{"method": "POST", "path": "/os-assisted-volume-snapshots"}], + ), + base.APIRule( + name="os_compute_api:os-assisted-volume-snapshots:delete", + check_str=("rule:system_admin_api"), + description="Delete an assisted volume snapshot", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/os-assisted-volume-snapshots/{snapshot_id}"}], + ), + base.APIRule( + name="os_compute_api:os-attach-interfaces:list", + check_str=("rule:system_or_project_reader"), + description="List port interfaces attached to a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/os-interface"}], + ), + base.APIRule( + name="os_compute_api:os-attach-interfaces:show", + check_str=("rule:system_or_project_reader"), + description="Show details of a port interface attached to a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/os-interface/{port_id}"}], + ), + base.APIRule( + name="os_compute_api:os-attach-interfaces:create", + check_str=("rule:system_admin_or_owner"), + description="Attach an interface to a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/os-interface"}], + ), + base.APIRule( + name="os_compute_api:os-attach-interfaces:delete", + check_str=("rule:system_admin_or_owner"), + description="Detach an interface from a server", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/servers/{server_id}/os-interface/{port_id}"}], + ), + base.APIRule( + name="os_compute_api:os-availability-zone:list", + check_str=("@"), + description="List availability zone information without host information", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-availability-zone"}], + ), + base.APIRule( + name="os_compute_api:os-availability-zone:detail", + check_str=("rule:system_reader_api"), + description="List detailed availability zone information with host information", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-availability-zone/detail"}], + ), + base.APIRule( + name="os_compute_api:os-baremetal-nodes:list", + check_str=("rule:system_reader_api"), + description="List and show details of bare metal nodes.\n#\n#These " + "APIs are proxy calls to the Ironic service and are " + "deprecated.\n#", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-baremetal-nodes"}], + ), + base.APIRule( + name="os_compute_api:os-baremetal-nodes:show", + check_str=("rule:system_reader_api"), + description="Show action details for a server.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-baremetal-nodes/{node_id}"}], + ), + base.APIRule( + name="os_compute_api:os-console-auth-tokens", + check_str=("rule:system_reader_api"), + description="Show console connection information " + "for a given console authentication token", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-console-auth-tokens/{console_token}"}], + ), + base.APIRule( + name="os_compute_api:os-console-output", + check_str=("rule:system_admin_or_owner"), + description="Show console output for a server", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers/{server_id}/action (os-getConsoleOutput)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-create-backup", + check_str=("rule:system_admin_or_owner"), + description="Create a back up of a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (createBackup)"}], + ), + base.APIRule( + name="os_compute_api:os-deferred-delete:restore", + check_str=("rule:system_admin_or_owner"), + description="Restore a soft deleted server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (restore)"}], + ), + base.APIRule( + name="os_compute_api:os-deferred-delete:force", + check_str=("rule:system_admin_or_owner"), + description="Force delete a server before deferred cleanup", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (forceDelete)"}], + ), + base.APIRule( + name="os_compute_api:os-evacuate", + check_str=("rule:system_admin_api"), + description="Evacuate a server from a failed host to a new host", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (evacuate)"}], + ), + base.APIRule( + name="os_compute_api:os-extended-server-attributes", + check_str=("rule:system_admin_api"), + description="Return extended attributes for server.\n#\n#This rule " + "will control the visibility for a set of servers " + "attributes:\n#\n#- ``OS-EXT-SRV-ATTR:host``\n#- " + "``OS-EXT-SRV-ATTR:instance_name``\n#- " + "``OS-EXT-SRV-ATTR:reservation_id`` (since microversion " + "2.3)\n#- ``OS-EXT-SRV-ATTR:launch_index`` (since " + "microversion 2.3)\n#- ``OS-EXT-SRV-ATTR:hostname`` (" + "since microversion 2.3)\n#- " + "``OS-EXT-SRV-ATTR:kernel_id`` (since microversion " + "2.3)\n#- ``OS-EXT-SRV-ATTR:ramdisk_id`` (since " + "microversion 2.3)\n#- " + "``OS-EXT-SRV-ATTR:root_device_name`` (since " + "microversion 2.3)\n#- ``OS-EXT-SRV-ATTR:user_data`` (" + "since microversion 2.3)\n#\n#Microvision 2.75 added the " + "above attributes in the ``PUT /servers/{" + "server_id}``\n#and ``POST /servers/{server_id}/action (" + "rebuild)`` API responses which are\n#also controlled by " + "this policy rule, like the ``GET /servers*`` APIs.\n#", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{id}"}, + {"method": "GET", "path": "/servers/detail"}, + {"method": "PUT", "path": "/servers/{server_id}"}, + {"method": "POST", "path": "/servers/{server_id}/action (rebuild)"}, + ], + ), + base.APIRule( + name="os_compute_api:extensions", + check_str=("@"), + description="List available extensions and show information for an extension by alias", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/extensions"}, + {"method": "GET", "path": "/extensions/{alias}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-flavor-access:add_tenant_access", + check_str=("rule:system_admin_api"), + description="Add flavor access to a tenant", + scope_types=["system"], + operations=[{"method": "POST", "path": "/flavors/{flavor_id}/action (addTenantAccess)"}], + ), + base.APIRule( + name="os_compute_api:os-flavor-access:remove_tenant_access", + check_str=("rule:system_admin_api"), + description="Remove flavor access from a tenant", + scope_types=["system"], + operations=[ + {"method": "POST", "path": "/flavors/{flavor_id}/action (removeTenantAccess)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-flavor-access", + check_str=("rule:system_reader_api"), + description="List flavor access information\n#\n#Allows access to " + "the full list of tenants that have access\n#to a flavor " + "via an os-flavor-access API.\n#", + scope_types=["system"], + operations=[{"method": "GET", "path": "/flavors/{flavor_id}/os-flavor-access"}], + ), + base.APIRule( + name="os_compute_api:os-flavor-extra-specs:show", + check_str=("rule:system_or_project_reader"), + description="Show an extra spec for a flavor", + scope_types=["system", "project"], + operations=[ + { + "method": "GET", + "path": "/flavors/{flavor_id}/os-extra_specs/{flavor_extra_spec_key}", + }, + ], + ), + base.APIRule( + name="os_compute_api:os-flavor-extra-specs:create", + check_str=("rule:system_admin_api"), + description="Create extra specs for a flavor", + scope_types=["system"], + operations=[{"method": "POST", "path": "/flavors/{flavor_id}/os-extra_specs/"}], + ), + base.APIRule( + name="os_compute_api:os-flavor-extra-specs:update", + check_str=("rule:system_admin_api"), + description="Update an extra spec for a flavor", + scope_types=["system"], + operations=[ + { + "method": "PUT", + "path": "/flavors/{flavor_id}/os-extra_specs/{flavor_extra_spec_key}", + }, + ], + ), + base.APIRule( + name="os_compute_api:os-flavor-extra-specs:delete", + check_str=("rule:system_admin_api"), + description="Delete an extra spec for a flavor", + scope_types=["system"], + operations=[ + { + "method": "DELETE", + "path": "/flavors/{flavor_id}/os-extra_specs/{flavor_extra_spec_key}", + }, + ], + ), + base.APIRule( + name="os_compute_api:os-flavor-extra-specs:index", + check_str=("rule:system_or_project_reader"), + description="List extra specs for a flavor. Starting with " + "microversion 2.47, the flavor used for a server is also " + "returned in the response when showing server details, " + "updating a server or rebuilding a server. Starting with " + "microversion 2.61, extra specs may be returned in " + "responses for the flavor resource.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/flavors/{flavor_id}/os-extra_specs/"}, + {"method": "GET", "path": "/servers/detail"}, + {"method": "GET", "path": "/servers/{server_id}"}, + {"method": "PUT", "path": "/servers/{server_id}"}, + {"method": "POST", "path": "/servers/{server_id}/action (rebuild)"}, + {"method": "POST", "path": "/flavors"}, + {"method": "GET", "path": "/flavors/detail"}, + {"method": "GET", "path": "/flavors/{flavor_id}"}, + {"method": "PUT", "path": "/flavors/{flavor_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-flavor-manage:create", + check_str=("rule:system_admin_api"), + description="Create a flavor", + scope_types=["system"], + operations=[{"method": "POST", "path": "/flavors"}], + ), + base.APIRule( + name="os_compute_api:os-flavor-manage:update", + check_str=("rule:system_admin_api"), + description="Update a flavor", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/flavors/{flavor_id}"}], + ), + base.APIRule( + name="os_compute_api:os-flavor-manage:delete", + check_str=("rule:system_admin_api"), + description="Delete a flavor", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/flavors/{flavor_id}"}], + ), + base.APIRule( + name="os_compute_api:os-floating-ip-pools", + check_str=("@"), + description="List floating IP pools. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-floating-ip-pools"}], + ), + base.APIRule( + name="os_compute_api:os-floating-ips:add", + check_str=("rule:system_admin_or_owner"), + description="Associate floating IPs to server. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (addFloatingIp)"}], + ), + base.APIRule( + name="os_compute_api:os-floating-ips:remove", + check_str=("rule:system_admin_or_owner"), + description="Disassociate floating IPs to server. This API is deprecated.", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers/{server_id}/action (removeFloatingIp)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-floating-ips:list", + check_str=("rule:system_or_project_reader"), + description="List floating IPs. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-floating-ips"}], + ), + base.APIRule( + name="os_compute_api:os-floating-ips:create", + check_str=("rule:system_admin_or_owner"), + description="Create floating IPs. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/os-floating-ips"}], + ), + base.APIRule( + name="os_compute_api:os-floating-ips:show", + check_str=("rule:system_or_project_reader"), + description="Show floating IPs. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-floating-ips/{floating_ip_id}"}], + ), + base.APIRule( + name="os_compute_api:os-floating-ips:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete floating IPs. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/os-floating-ips/{floating_ip_id}"}], + ), + base.APIRule( + name="os_compute_api:os-hosts:list", + check_str=("rule:system_reader_api"), + description="List physical hosts.\n#\n#This API is deprecated in " + "favor of os-hypervisors and os-services.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hosts"}], + ), + base.APIRule( + name="os_compute_api:os-hosts:show", + check_str=("rule:system_reader_api"), + description="Show physical host.\n#\n#This API is " + "deprecated in favor of os-hypervisors and os-services.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hosts/{host_name}"}], + ), + base.APIRule( + name="os_compute_api:os-hosts:update", + check_str=("rule:system_admin_api"), + description="Update physical host.\n#\n#This API is " + "deprecated in favor of os-hypervisors and os-services.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/os-hosts/{host_name}"}], + ), + base.APIRule( + name="os_compute_api:os-hosts:reboot", + check_str=("rule:system_admin_api"), + description="Reboot physical host.\n#\n#This API is deprecated " + "in favor of os-hypervisors and os-services.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hosts/{host_name}/reboot"}], + ), + base.APIRule( + name="os_compute_api:os-hosts:shutdown", + check_str=("rule:system_admin_api"), + description="Shutdown physical host.\n#\n#This API is deprecated " + "in favor of os-hypervisors and os-services.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hosts/{host_name}/shutdown"}], + ), + base.APIRule( + name="os_compute_api:os-hosts:start", + check_str=("rule:system_admin_api"), + description="Start physical host.\n#\n#This API is deprecated " + "in favor of os-hypervisors and os-services.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hosts/{host_name}/startup"}], + ), + base.APIRule( + name="os_compute_api:os-hypervisors:list", + check_str=("rule:system_reader_api"), + description="List all hypervisors.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hypervisors"}], + ), + base.APIRule( + name="os_compute_api:os-hypervisors:list-detail", + check_str=("rule:system_reader_api"), + description="List all hypervisors with details", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hypervisors/details"}], + ), + base.APIRule( + name="os_compute_api:os-hypervisors:statistics", + check_str=("rule:system_reader_api"), + description="Show summary statistics for all hypervisors over all compute nodes.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hypervisors/statistics"}], + ), + base.APIRule( + name="os_compute_api:os-hypervisors:show", + check_str=("rule:system_reader_api"), + description="Show details for a hypervisor.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hypervisors/{hypervisor_id}"}], + ), + base.APIRule( + name="os_compute_api:os-hypervisors:uptime", + check_str=("rule:system_reader_api"), + description="Show the uptime of a hypervisor.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-hypervisors/{hypervisor_id}/uptime"}], + ), + base.APIRule( + name="os_compute_api:os-hypervisors:search", + check_str=("rule:system_reader_api"), + description="Search hypervisor by hypervisor_hostname pattern.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/os-hypervisors/{hypervisor_hostname_pattern}/search"}, + ], + ), + base.APIRule( + name="os_compute_api:os-hypervisors:servers", + check_str=("rule:system_reader_api"), + description="List all servers on hypervisors that can match the " + "provided hypervisor_hostname pattern.", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/os-hypervisors/{hypervisor_hostname_pattern}/servers"}, + ], + ), + base.APIRule( + name="os_compute_api:os-instance-actions:events:details", + check_str=("rule:system_reader_api"), + description='Add "details" key in action events for a ' + "server.\n#\n#This check is performed only after the " + "check\n#os_compute_api:os-instance-actions:show passes. " + "Beginning with Microversion\n#2.84, new field 'details' " + "is exposed via API which can have more details " + "about\n#event failure. That field is controlled by this " + "policy which is system reader\n#by default. Making the " + "'details' field visible to the non-admin user helps " + "to\n#understand the nature of the problem (i.e. if the " + "action can be retried),\n#but in the other hand it " + "might leak information about the deployment\n#(e.g. the " + "type of the hypervisor).\n#", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{server_id}/os-instance-actions/{request_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-instance-actions:events", + check_str=("rule:system_reader_api"), + description="Add events details in action details for a " + "server.\n#This check is performed only after the " + "check\n#os_compute_api:os-instance-actions:show passes. " + "Beginning with Microversion\n#2.51, events details are " + "always included; traceback information is " + "provided\n#per event if policy enforcement passes. " + "Beginning with Microversion 2.62,\n#each event includes " + "a hashed host identifier and, if policy " + "enforcement\n#passes, the name of the host.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{server_id}/os-instance-actions/{request_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-instance-actions:list", + check_str=("rule:system_or_project_reader"), + description="List actions for a server.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/os-instance-actions"}], + ), + base.APIRule( + name="os_compute_api:os-instance-actions:show", + check_str=("rule:system_or_project_reader"), + description="Show action details for a server.", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{server_id}/os-instance-actions/{request_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-instance-usage-audit-log:list", + check_str=("rule:system_reader_api"), + description="List all usage audits.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-instance_usage_audit_log"}], + ), + base.APIRule( + name="os_compute_api:os-instance-usage-audit-log:show", + check_str=("rule:system_reader_api"), + description="List all usage audits occurred before a specified time " + "for all servers on all compute hosts where usage " + "auditing is configured", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/os-instance_usage_audit_log/{before_timestamp}"}, + ], + ), + base.APIRule( + name="os_compute_api:ips:show", + check_str=("rule:system_or_project_reader"), + description="Show IP addresses details for a network label of a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/ips/{network_label}"}], + ), + base.APIRule( + name="os_compute_api:ips:index", + check_str=("rule:system_or_project_reader"), + description="List IP addresses that are assigned to a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/ips"}], + ), + base.APIRule( + name="os_compute_api:os-keypairs:index", + check_str=("(rule:system_reader_api) or user_id:%(user_id)s"), + description="List all keypairs", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-keypairs"}], + ), + base.APIRule( + name="os_compute_api:os-keypairs:create", + check_str=("(rule:system_admin_api) or user_id:%(user_id)s"), + description="Create a keypair", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/os-keypairs"}], + ), + base.APIRule( + name="os_compute_api:os-keypairs:delete", + check_str=("(rule:system_admin_api) or user_id:%(user_id)s"), + description="Delete a keypair", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/os-keypairs/{keypair_name}"}], + ), + base.APIRule( + name="os_compute_api:os-keypairs:show", + check_str=("(rule:system_reader_api) or user_id:%(user_id)s"), + description="Show details of a keypair", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-keypairs/{keypair_name}"}], + ), + base.APIRule( + name="os_compute_api:limits", + check_str=("@"), + description="Show rate and absolute limits for the current user project", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/limits"}], + ), + base.APIRule( + name="os_compute_api:limits:other_project", + check_str=("rule:system_reader_api"), + description="Show rate and absolute limits of other " + "project.\n#\n#This policy only checks if the user has " + "access to the requested\n#project limits. And this " + "check is performed only after the " + "check\n#os_compute_api:limits passes", + scope_types=["system"], + operations=[{"method": "GET", "path": "/limits"}], + ), + base.APIRule( + name="os_compute_api:os-lock-server:lock", + check_str=("rule:system_admin_or_owner"), + description="Lock a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (lock)"}], + ), + base.APIRule( + name="os_compute_api:os-lock-server:unlock", + check_str=("rule:system_admin_or_owner"), + description="Unlock a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (unlock)"}], + ), + base.APIRule( + name="os_compute_api:os-lock-server:unlock:unlock_override", + check_str=("rule:system_admin_api"), + description="Unlock a server, regardless who locked the " + "server.\n#\n#This check is performed only after the " + "check\n#os_compute_api:os-lock-server:unlock passes", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (unlock)"}], + ), + base.APIRule( + name="os_compute_api:os-migrate-server:migrate", + check_str=("rule:system_admin_api"), + description="Cold migrate a server to a host", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (migrate)"}], + ), + base.APIRule( + name="os_compute_api:os-migrate-server:migrate_live", + check_str=("rule:system_admin_api"), + description="Live migrate a server to a new host without a reboot", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (os-migrateLive)"}], + ), + base.APIRule( + name="os_compute_api:os-migrations:index", + check_str=("rule:system_reader_api"), + description="List migrations", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-migrations"}], + ), + base.APIRule( + name="os_compute_api:os-multinic:add", + check_str=("rule:system_admin_or_owner"), + description="Add a fixed IP address to a server.\n#\n#This API is " + "proxy calls to the Network service. This " + "is\n#deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (addFixedIp)"}], + ), + base.APIRule( + name="os_compute_api:os-multinic:remove", + check_str=("rule:system_admin_or_owner"), + description="Remove a fixed IP address from a server.\n#\n#This API " + "is proxy calls to the Network service. This " + "is\n#deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (removeFixedIp)"}], + ), + base.APIRule( + name="os_compute_api:os-networks:list", + check_str=("rule:system_or_project_reader"), + description="List networks for the project.\n#\n#This API is proxy " + "calls to the Network service. This is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-networks"}], + ), + base.APIRule( + name="os_compute_api:os-networks:show", + check_str=("rule:system_or_project_reader"), + description="Show network details.\n#\n#This API is proxy calls to " + "the Network service. This is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-networks/{network_id}"}], + ), + base.APIRule( + name="os_compute_api:os-pause-server:pause", + check_str=("rule:system_admin_or_owner"), + description="Pause a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (pause)"}], + ), + base.APIRule( + name="os_compute_api:os-pause-server:unpause", + check_str=("rule:system_admin_or_owner"), + description="Unpause a paused server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (unpause)"}], + ), + base.APIRule( + name="os_compute_api:os-quota-class-sets:show", + check_str=("rule:system_reader_api"), + description="List quotas for specific quota classs", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-quota-class-sets/{quota_class}"}], + ), + base.APIRule( + name="os_compute_api:os-quota-class-sets:update", + check_str=("rule:system_admin_api"), + description="Update quotas for specific quota class", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/os-quota-class-sets/{quota_class}"}], + ), + base.APIRule( + name="os_compute_api:os-quota-sets:update", + check_str=("rule:system_admin_api"), + description="Update the quotas", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/os-quota-sets/{tenant_id}"}], + ), + base.APIRule( + name="os_compute_api:os-quota-sets:defaults", + check_str=("@"), + description="List default quotas", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-quota-sets/{tenant_id}/defaults"}], + ), + base.APIRule( + name="os_compute_api:os-quota-sets:show", + check_str=("rule:system_or_project_reader"), + description="Show a quota", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-quota-sets/{tenant_id}"}], + ), + base.APIRule( + name="os_compute_api:os-quota-sets:delete", + check_str=("rule:system_admin_api"), + description="Revert quotas to defaults", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/os-quota-sets/{tenant_id}"}], + ), + base.APIRule( + name="os_compute_api:os-quota-sets:detail", + check_str=("rule:system_or_project_reader"), + description="Show the detail of quota", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-quota-sets/{tenant_id}/detail"}], + ), + base.APIRule( + name="os_compute_api:os-remote-consoles", + check_str=("rule:system_admin_or_owner"), + description="Generate a URL to access remove server " + "console.\n#\n#This policy is for ``POST " + "/remote-consoles`` API and below Server actions " + "APIs\n#are deprecated:\n#\n#- ``os-getRDPConsole``\n#- " + "``os-getSerialConsole``\n#- ``os-getSPICEConsole``\n#- " + "``os-getVNCConsole``.", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers/{server_id}/action (os-getRDPConsole)"}, + {"method": "POST", "path": "/servers/{server_id}/action (os-getSerialConsole)"}, + {"method": "POST", "path": "/servers/{server_id}/action (os-getSPICEConsole)"}, + {"method": "POST", "path": "/servers/{server_id}/action (os-getVNCConsole)"}, + {"method": "POST", "path": "/servers/{server_id}/remote-consoles"}, + ], + ), + base.APIRule( + name="os_compute_api:os-rescue", + check_str=("rule:system_admin_or_owner"), + description="Rescue a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (rescue)"}], + ), + base.APIRule( + name="os_compute_api:os-unrescue", + check_str=("rule:system_admin_or_owner"), + description="Unrescue a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (unrescue)"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:get", + check_str=("rule:system_or_project_reader"), + description="List security groups. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-security-groups"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:show", + check_str=("rule:system_or_project_reader"), + description="Show security group. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-security-groups/{security_group_id}"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:create", + check_str=("rule:system_admin_or_owner"), + description="Create security group. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/os-security-groups"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:update", + check_str=("rule:system_admin_or_owner"), + description="Update security group. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/os-security-groups/{security_group_id}"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete security group. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/os-security-groups/{security_group_id}"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:rule:create", + check_str=("rule:system_admin_or_owner"), + description="Create security group Rule. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/os-security-group-rules"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:rule:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete security group Rule. This API is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/os-security-group-rules/{security_group_id}"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:list", + check_str=("rule:system_or_project_reader"), + description="List security groups of server.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/os-security-groups"}], + ), + base.APIRule( + name="os_compute_api:os-security-groups:add", + check_str=("rule:system_admin_or_owner"), + description="Add security groups to server.", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers/{server_id}/action (addSecurityGroup)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-security-groups:remove", + check_str=("rule:system_admin_or_owner"), + description="Remove security groups from server.", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers/{server_id}/action (removeSecurityGroup)"}, + ], + ), + base.APIRule( + name="os_compute_api:os-server-diagnostics", + check_str=("rule:system_admin_api"), + description="Show the usage data for a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/diagnostics"}], + ), + base.APIRule( + name="os_compute_api:os-server-external-events:create", + check_str=("rule:system_admin_api"), + description="Create one or more external events", + scope_types=["system"], + operations=[{"method": "POST", "path": "/os-server-external-events"}], + ), + base.APIRule( + name="os_compute_api:os-server-groups:create", + check_str=("rule:project_member_api"), + description="Create a new server group", + scope_types=["project"], + operations=[{"method": "POST", "path": "/os-server-groups"}], + ), + base.APIRule( + name="os_compute_api:os-server-groups:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete a server group", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/os-server-groups/{server_group_id}"}], + ), + base.APIRule( + name="os_compute_api:os-server-groups:index", + check_str=("rule:system_or_project_reader"), + description="List all server groups", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-server-groups"}], + ), + base.APIRule( + name="os_compute_api:os-server-groups:index:all_projects", + check_str=("rule:system_reader_api"), + description="List all server groups for all projects", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-server-groups"}], + ), + base.APIRule( + name="os_compute_api:os-server-groups:show", + check_str=("rule:system_or_project_reader"), + description="Show details of a server group", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-server-groups/{server_group_id}"}], + ), + base.APIRule( + name="os_compute_api:server-metadata:index", + check_str=("rule:system_or_project_reader"), + description="List all metadata of a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/metadata"}], + ), + base.APIRule( + name="os_compute_api:server-metadata:show", + check_str=("rule:system_or_project_reader"), + description="Show metadata for a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/metadata/{key}"}], + ), + base.APIRule( + name="os_compute_api:server-metadata:create", + check_str=("rule:system_admin_or_owner"), + description="Create metadata for a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/metadata"}], + ), + base.APIRule( + name="os_compute_api:server-metadata:update_all", + check_str=("rule:system_admin_or_owner"), + description="Replace metadata for a server", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/servers/{server_id}/metadata"}], + ), + base.APIRule( + name="os_compute_api:server-metadata:update", + check_str=("rule:system_admin_or_owner"), + description="Update metadata from a server", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/servers/{server_id}/metadata/{key}"}], + ), + base.APIRule( + name="os_compute_api:server-metadata:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete metadata from a server", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/servers/{server_id}/metadata/{key}"}], + ), + base.APIRule( + name="os_compute_api:os-server-password:show", + check_str=("rule:system_or_project_reader"), + description="Show the encrypted administrative password of a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/os-server-password"}], + ), + base.APIRule( + name="os_compute_api:os-server-password:clear", + check_str=("rule:system_admin_or_owner"), + description="Clear the encrypted administrative password of a server", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/servers/{server_id}/os-server-password"}], + ), + base.APIRule( + name="os_compute_api:os-server-tags:delete_all", + check_str=("rule:system_admin_or_owner"), + description="Delete all the server tags", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/servers/{server_id}/tags"}], + ), + base.APIRule( + name="os_compute_api:os-server-tags:index", + check_str=("rule:system_or_project_reader"), + description="List all tags for given server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/tags"}], + ), + base.APIRule( + name="os_compute_api:os-server-tags:update_all", + check_str=("rule:system_admin_or_owner"), + description="Replace all tags on specified server with the new set of tags.", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/servers/{server_id}/tags"}], + ), + base.APIRule( + name="os_compute_api:os-server-tags:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete a single tag from the specified server", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/servers/{server_id}/tags/{tag}"}], + ), + base.APIRule( + name="os_compute_api:os-server-tags:update", + check_str=("rule:system_admin_or_owner"), + description="Add a single tag to the server if server has no specified tag", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/servers/{server_id}/tags/{tag}"}], + ), + base.APIRule( + name="os_compute_api:os-server-tags:show", + check_str=("rule:system_or_project_reader"), + description="Check tag existence on the server.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/tags/{tag}"}], + ), + base.APIRule( + name="compute:server:topology:index", + check_str=("rule:system_or_project_reader"), + description="Show the NUMA topology data for a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/topology"}], + ), + base.APIRule( + name="compute:server:topology:host:index", + check_str=("rule:system_reader_api"), + description="Show the NUMA topology data for a server with host NUMA " + "ID and CPU pinning information", + scope_types=["system"], + operations=[{"method": "GET", "path": "/servers/{server_id}/topology"}], + ), + base.APIRule( + name="os_compute_api:servers:index", + check_str=("rule:system_or_project_reader"), + description="List all servers", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers"}], + ), + base.APIRule( + name="os_compute_api:servers:detail", + check_str=("rule:system_or_project_reader"), + description="List all servers with detailed information", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/detail"}], + ), + base.APIRule( + name="os_compute_api:servers:index:get_all_tenants", + check_str=("rule:system_reader_api"), + description="List all servers for all projects", + scope_types=["system"], + operations=[{"method": "GET", "path": "/servers"}], + ), + base.APIRule( + name="os_compute_api:servers:detail:get_all_tenants", + check_str=("rule:system_reader_api"), + description="List all servers with detailed information for all projects", + scope_types=["system"], + operations=[{"method": "GET", "path": "/servers/detail"}], + ), + base.APIRule( + name="os_compute_api:servers:allow_all_filters", + check_str=("rule:system_reader_api"), + description="Allow all filters when listing servers", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/servers"}, + {"method": "GET", "path": "/servers/detail"}, + ], + ), + base.APIRule( + name="os_compute_api:servers:show", + check_str=("rule:system_or_project_reader"), + description="Show a server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}"}], + ), + base.APIRule( + name="os_compute_api:servers:show:host_status", + check_str=("rule:system_admin_api"), + description="\n#Show a server with additional host status " + "information.\n#\n#This means host_status will be shown " + "irrespective of status value. If showing\n#only " + "host_status UNKNOWN is desired, " + "use the\n#``os_compute_api:servers:show:host_status" + ":unknown-only`` policy rule.\n#\n#Microvision 2.75 " + "added the ``host_status`` attribute in the\n#``PUT " + "/servers/{server_id}`` and ``POST /servers/{" + "server_id}/action (rebuild)``\n#API responses which are " + "also controlled by this policy rule, like the\n#``GET " + "/servers*`` APIs.\n#", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{server_id}"}, + {"method": "GET", "path": "/servers/detail"}, + {"method": "PUT", "path": "/servers/{server_id}"}, + {"method": "POST", "path": "/servers/{server_id}/action (rebuild)"}, + ], + ), + base.APIRule( + name="os_compute_api:servers:show:host_status:unknown-only", + check_str=("rule:system_admin_api"), + description="\n#Show a server with additional host status " + "information, only if host status " + "is\n#UNKNOWN.\n#\n#This policy rule will only be " + "enforced when " + "the\n#``os_compute_api:servers:show:host_status`` " + "policy rule does not pass for the\n#request. An example " + "policy configuration could be where " + "the\n#``os_compute_api:servers:show:host_status`` rule " + "is set to allow admin-only and\n#the " + "``os_compute_api:servers:show:host_status:unknown-only" + "`` rule is set to\n#allow everyone.\n#", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{server_id}"}, + {"method": "GET", "path": "/servers/detail"}, + {"method": "PUT", "path": "/servers/{server_id}"}, + {"method": "POST", "path": "/servers/{server_id}/action (rebuild)"}, + ], + ), + base.APIRule( + name="os_compute_api:servers:create", + check_str=("rule:project_member_api"), + description="Create a server", + scope_types=["project"], + operations=[{"method": "POST", "path": "/servers"}], + ), + base.APIRule( + name="os_compute_api:servers:create:forced_host", + check_str=("rule:project_admin_api"), + description="\n#Create a server on the specified host and/or " + "node.\n#\n#In this case, the server is forced to launch " + "on the specified\n#host and/or node by bypassing the " + "scheduler filters unlike " + "the\n#``compute:servers:create:requested_destination`` " + "rule.\n#", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers"}], + ), + base.APIRule( + name="compute:servers:create:requested_destination", + check_str=("rule:project_admin_api"), + description="\n#Create a server on the requested compute service " + "host and/or\n#hypervisor_hostname.\n#\n#In this case, " + "the requested host and/or hypervisor_hostname " + "is\n#validated by the scheduler filters unlike " + "the\n#``os_compute_api:servers:create:forced_host`` " + "rule.\n#", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers"}], + ), + base.APIRule( + name="os_compute_api:servers:create:attach_volume", + check_str=("rule:project_member_api"), + description="Create a server with the requested volume attached to it", + scope_types=["project"], + operations=[{"method": "POST", "path": "/servers"}], + ), + base.APIRule( + name="os_compute_api:servers:create:attach_network", + check_str=("rule:project_member_api"), + description="Create a server with the requested network attached to it", + scope_types=["project"], + operations=[{"method": "POST", "path": "/servers"}], + ), + base.APIRule( + name="os_compute_api:servers:create:trusted_certs", + check_str=("rule:project_member_api"), + description="Create a server with trusted image certificate IDs", + scope_types=["project"], + operations=[{"method": "POST", "path": "/servers"}], + ), + base.APIRule( + name="os_compute_api:servers:create:zero_disk_flavor", + check_str=("rule:project_admin_api"), + description="\n#This rule controls the compute API validation " + "behavior of creating a server\n#with a flavor that has " + "0 disk, indicating the server should be " + "volume-backed.\n#\n#For a flavor with disk=0, the root " + "disk will be set to exactly the size of the\n#image " + "used to deploy the instance. However, in this case the " + "filter_scheduler\n#cannot select the compute host based " + "on the virtual image size. Therefore, 0\n#should only " + "be used for volume booted instances or for testing " + "purposes.\n#\n#WARNING: It is a potential security " + "exposure to enable this policy rule\n#if users can " + "upload their own images since repeated attempts " + "to\n#create a disk=0 flavor instance with a large image " + "can exhaust\n#the local disk of the compute (or shared " + "storage cluster). See " + "bug\n#https://bugs.launchpad.net/nova/+bug/1739646 for " + "details.\n#", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers"}], + ), + base.APIRule( + name="network:attach_external_network", + check_str=("rule:project_admin_api"), + description="Attach an unshared external network to a server", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers"}, + {"method": "POST", "path": "/servers/{server_id}/os-interface"}, + ], + ), + base.APIRule( + name="os_compute_api:servers:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete a server", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/servers/{server_id}"}], + ), + base.APIRule( + name="os_compute_api:servers:update", + check_str=("rule:system_admin_or_owner"), + description="Update a server", + scope_types=["system", "project"], + operations=[{"method": "PUT", "path": "/servers/{server_id}"}], + ), + base.APIRule( + name="os_compute_api:servers:confirm_resize", + check_str=("rule:system_admin_or_owner"), + description="Confirm a server resize", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (confirmResize)"}], + ), + base.APIRule( + name="os_compute_api:servers:revert_resize", + check_str=("rule:system_admin_or_owner"), + description="Revert a server resize", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (revertResize)"}], + ), + base.APIRule( + name="os_compute_api:servers:reboot", + check_str=("rule:system_admin_or_owner"), + description="Reboot a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (reboot)"}], + ), + base.APIRule( + name="os_compute_api:servers:resize", + check_str=("rule:system_admin_or_owner"), + description="Resize a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (resize)"}], + ), + base.APIRule( + name="compute:servers:resize:cross_cell", + check_str=("!"), + description="Resize a server across cells. By default, this is " + "disabled for all users and recommended to be tested in " + "a deployment for admin users before opening it up to " + "non-admin users. Resizing within a cell is the default " + "preferred behavior even if this is enabled. ", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (resize)"}], + ), + base.APIRule( + name="os_compute_api:servers:rebuild", + check_str=("rule:system_admin_or_owner"), + description="Rebuild a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (rebuild)"}], + ), + base.APIRule( + name="os_compute_api:servers:rebuild:trusted_certs", + check_str=("rule:system_admin_or_owner"), + description="Rebuild a server with trusted image certificate IDs", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (rebuild)"}], + ), + base.APIRule( + name="os_compute_api:servers:create_image", + check_str=("rule:system_admin_or_owner"), + description="Create an image from a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (createImage)"}], + ), + base.APIRule( + name="os_compute_api:servers:create_image:allow_volume_backed", + check_str=("rule:system_admin_or_owner"), + description="Create an image from a volume backed server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (createImage)"}], + ), + base.APIRule( + name="os_compute_api:servers:start", + check_str=("rule:system_admin_or_owner"), + description="Start a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (os-start)"}], + ), + base.APIRule( + name="os_compute_api:servers:stop", + check_str=("rule:system_admin_or_owner"), + description="Stop a server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (os-stop)"}], + ), + base.APIRule( + name="os_compute_api:servers:trigger_crash_dump", + check_str=("rule:system_admin_or_owner"), + description="Trigger crash dump in a server", + scope_types=["system", "project"], + operations=[ + {"method": "POST", "path": "/servers/{server_id}/action (trigger_crash_dump)"}, + ], + ), + base.APIRule( + name="os_compute_api:servers:migrations:show", + check_str=("rule:system_reader_api"), + description="Show details for an in-progress live migration for a given server", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{server_id}/migrations/{migration_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:servers:migrations:force_complete", + check_str=("rule:system_admin_api"), + description="Force an in-progress live migration for a given server to complete", + scope_types=["system", "project"], + operations=[ + { + "method": "POST", + "path": "/servers/{server_id}/migrations/{migration_id}/" + "action (force_complete)", + }, + ], + ), + base.APIRule( + name="os_compute_api:servers:migrations:delete", + check_str=("rule:system_admin_api"), + description="Delete(Abort) an in-progress live migration", + scope_types=["system", "project"], + operations=[ + {"method": "DELETE", "path": "/servers/{server_id}/migrations/{migration_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:servers:migrations:index", + check_str=("rule:system_reader_api"), + description="Lists in-progress live migrations for a given server", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/migrations"}], + ), + base.APIRule( + name="os_compute_api:os-services:list", + check_str=("rule:system_reader_api"), + description="List all running Compute services in a region.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-services"}], + ), + base.APIRule( + name="os_compute_api:os-services:update", + check_str=("rule:system_admin_api"), + description="Update a Compute service.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/os-services/{service_id}"}], + ), + base.APIRule( + name="os_compute_api:os-services:delete", + check_str=("rule:system_admin_api"), + description="Delete a Compute service.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/os-services/{service_id}"}], + ), + base.APIRule( + name="os_compute_api:os-shelve:shelve", + check_str=("rule:system_admin_or_owner"), + description="Shelve server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (shelve)"}], + ), + base.APIRule( + name="os_compute_api:os-shelve:unshelve", + check_str=("rule:system_admin_or_owner"), + description="Unshelve (restore) shelved server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (unshelve)"}], + ), + base.APIRule( + name="os_compute_api:os-shelve:shelve_offload", + check_str=("rule:system_admin_api"), + description="Shelf-offload (remove) server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (shelveOffload)"}], + ), + base.APIRule( + name="os_compute_api:os-simple-tenant-usage:show", + check_str=("rule:system_or_project_reader"), + description="Show usage statistics for a specific tenant", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-simple-tenant-usage/{tenant_id}"}], + ), + base.APIRule( + name="os_compute_api:os-simple-tenant-usage:list", + check_str=("rule:system_reader_api"), + description="List per tenant usage statistics for all tenants", + scope_types=["system"], + operations=[{"method": "GET", "path": "/os-simple-tenant-usage"}], + ), + base.APIRule( + name="os_compute_api:os-suspend-server:resume", + check_str=("rule:system_admin_or_owner"), + description="Resume suspended server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (resume)"}], + ), + base.APIRule( + name="os_compute_api:os-suspend-server:suspend", + check_str=("rule:system_admin_or_owner"), + description="Suspend server", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/action (suspend)"}], + ), + base.APIRule( + name="os_compute_api:os-tenant-networks:list", + check_str=("rule:system_or_project_reader"), + description="List project networks.\n#\n#This API is proxy calls to " + "the Network service. This is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-tenant-networks"}], + ), + base.APIRule( + name="os_compute_api:os-tenant-networks:show", + check_str=("rule:system_or_project_reader"), + description="Show project network details.\n#\n#This API is proxy " + "calls to the Network service. This is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-tenant-networks/{network_id}"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:list", + check_str=("rule:system_or_project_reader"), + description="List volumes.\n#\n#This API is a proxy call to " + "the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-volumes"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:create", + check_str=("rule:system_admin_or_owner"), + description="Create volume.\n#\n#This API is a proxy call to the " + "Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/os-volumes"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:detail", + check_str=("rule:system_or_project_reader"), + description="List volumes detail.\n#\n#This API is a proxy call to " + "the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-volumes/detail"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:show", + check_str=("rule:system_or_project_reader"), + description="Show volume.\n#\n#This API is a proxy call to " + "the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-volumes/{volume_id}"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete volume.\n#\n#This API is a proxy call to " + "the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/os-volumes/{volume_id}"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:snapshots:list", + check_str=("rule:system_or_project_reader"), + description="List snapshots.\n#\n#This API is a proxy call " + "to the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-snapshots"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:snapshots:create", + check_str=("rule:system_admin_or_owner"), + description="Create snapshots.\n#\n#This API is a proxy call " + "to the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/os-snapshots"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:snapshots:detail", + check_str=("rule:system_or_project_reader"), + description="List snapshots details.\n#\n#This API is a proxy call " + "to the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-snapshots/detail"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:snapshots:show", + check_str=("rule:system_or_project_reader"), + description="Show snapshot.\n#\n#This API is a proxy call to " + "the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/os-snapshots/{snapshot_id}"}], + ), + base.APIRule( + name="os_compute_api:os-volumes:snapshots:delete", + check_str=("rule:system_admin_or_owner"), + description="Delete snapshot.\n#\n#This API is a proxy call " + "to the Volume service. It is deprecated.", + scope_types=["system", "project"], + operations=[{"method": "DELETE", "path": "/os-snapshots/{snapshot_id}"}], + ), + base.APIRule( + name="os_compute_api:os-volumes-attachments:index", + check_str=("rule:system_or_project_reader"), + description="List volume attachments for an instance", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/servers/{server_id}/os-volume_attachments"}], + ), + base.APIRule( + name="os_compute_api:os-volumes-attachments:create", + check_str=("rule:system_admin_or_owner"), + description="Attach a volume to an instance", + scope_types=["system", "project"], + operations=[{"method": "POST", "path": "/servers/{server_id}/os-volume_attachments"}], + ), + base.APIRule( + name="os_compute_api:os-volumes-attachments:show", + check_str=("rule:system_or_project_reader"), + description="Show details of a volume attachment", + scope_types=["system", "project"], + operations=[ + {"method": "GET", "path": "/servers/{server_id}/os-volume_attachments/{volume_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-volumes-attachments:update", + check_str=("rule:system_admin_or_owner"), + description="Update a volume attachment.\n#New 'update' policy about " + "'swap + update' request (which is possible\n#only " + ">2.85) only is checked. We expect to be\n#always superset of this policy " + "permission.\n#", + scope_types=["system", "project"], + operations=[ + {"method": "PUT", "path": "/servers/{server_id}/os-volume_attachments/{volume_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-volumes-attachments:swap", + check_str=("rule:system_admin_api"), + description="Update a volume attachment with a different volumeId", + scope_types=["system"], + operations=[ + {"method": "PUT", "path": "/servers/{server_id}/os-volume_attachments/{volume_id}"}, + ], + ), + base.APIRule( + name="os_compute_api:os-volumes-attachments:delete", + check_str=("rule:system_admin_or_owner"), + description="Detach a volume from an instance", + scope_types=["system", "project"], + operations=[ + { + "method": "DELETE", + "path": "/servers/{server_id}/os-volume_attachments/{volume_id}", + }, + ], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/octavia.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/octavia.py new file mode 100644 index 0000000..265cef4 --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/octavia.py @@ -0,0 +1,700 @@ +from . import base + +list_rules = ( + base.Rule( + name="system-admin", + check_str=("role:admin and system_scope:all"), + description="No description", + ), + base.Rule( + name="system-reader", + check_str=("role:reader and system_scope:all"), + description="No description", + ), + base.Rule( + name="project-member", + check_str=("role:member and project_id:%(project_id)s"), + description="No description", + ), + base.Rule( + name="project-reader", + check_str=("role:reader and project_id:%(project_id)s"), + description="No description", + ), + base.Rule( + name="context_is_admin", + check_str=("role:load-balancer_admin or rule:system-admin"), + description="No description", + ), + base.Rule( + name="load-balancer:owner", + check_str=("project_id:%(project_id)s"), + description="No description", + ), + base.Rule( + name="load-balancer:observer_and_owner", + check_str=("role:load-balancer_observer and rule:project-reader"), + description="No description", + ), + base.Rule( + name="load-balancer:global_observer", + check_str=("role:load-balancer_global_observer or rule:system-reader"), + description="No description", + ), + base.Rule( + name="load-balancer:member_and_owner", + check_str=("role:load-balancer_member and rule:project-member"), + description="No description", + ), + base.Rule( + name="load-balancer:admin", + check_str=("is_admin:True or role:load-balancer_admin or rule:system-admin"), + description="No description", + ), + base.Rule( + name="load-balancer:read", + check_str=( + "rule:load-balancer:observer_and_owner " + "or rule:load-balancer:global_observer " + "or rule:load-balancer:member_and_owner " + "or rule:load-balancer:admin" + ), + description="No description", + ), + base.Rule( + name="load-balancer:read-global", + check_str=("rule:load-balancer:global_observer or rule:load-balancer:admin"), + description="No description", + ), + base.Rule( + name="load-balancer:write", + check_str=("rule:load-balancer:member_and_owner or rule:load-balancer:admin"), + description="No description", + ), + base.Rule( + name="load-balancer:read-quota", + check_str=( + "rule:load-balancer:observer_and_owner or " + "rule:load-balancer:global_observer or " + "rule:load-balancer:member_and_owner or " + "role:load-balancer_quota_admin or rule:load-balancer:admin " + ), + description="No description", + ), + base.Rule( + name="load-balancer:read-quota-global", + check_str=( + "rule:load-balancer:global_observer or " + "role:load-balancer_quota_admin or rule:load-balancer:admin " + ), + description="No description", + ), + base.Rule( + name="load-balancer:write-quota", + check_str="role:load-balancer_quota_admin or rule:load-balancer:admin", + description="No description", + ), + base.APIRule( + name="os_load-balancer_api:flavor:get_all", + check_str=("rule:load-balancer:read"), + description="List Flavors", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2.0/lbaas/flavors"}], + ), + base.APIRule( + name="os_load-balancer_api:flavor:post", + check_str=("rule:load-balancer:admin"), + description="Create a Flavor", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2.0/lbaas/flavors"}], + ), + base.APIRule( + name="os_load-balancer_api:flavor:put", + check_str=("rule:load-balancer:admin"), + description="Update a Flavor", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2.0/lbaas/flavors/{flavor_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:flavor:get_one", + check_str=("rule:load-balancer:read"), + description="Show Flavor details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2.0/lbaas/flavors/{flavor_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:flavor:delete", + check_str=("rule:load-balancer:admin"), + description="Remove a Flavor", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2.0/lbaas/flavors/{flavor_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:flavor-profile:get_all", + check_str=("rule:load-balancer:admin"), + description="List Flavor Profiles", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2.0/lbaas/flavorprofiles"}], + ), + base.APIRule( + name="os_load-balancer_api:flavor-profile:post", + check_str=("rule:load-balancer:admin"), + description="Create a Flavor Profile", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2.0/lbaas/flavorprofiles"}], + ), + base.APIRule( + name="os_load-balancer_api:flavor-profile:put", + check_str=("rule:load-balancer:admin"), + description="Update a Flavor Profile", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/v2.0/lbaas/flavorprofiles/{flavor_profile_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:flavor-profile:get_one", + check_str=("rule:load-balancer:admin"), + description="Show Flavor Profile details", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v2.0/lbaas/flavorprofiles/{flavor_profile_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:flavor-profile:delete", + check_str=("rule:load-balancer:admin"), + description="Remove a Flavor Profile", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/v2.0/lbaas/flavorprofiles/{flavor_profile_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone:get_all", + check_str=("rule:load-balancer:read"), + description="List Availability Zones", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2.0/lbaas/availabilityzones"}], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone:post", + check_str=("rule:load-balancer:admin"), + description="Create an Availability Zone", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2.0/lbaas/availabilityzones"}], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone:put", + check_str=("rule:load-balancer:admin"), + description="Update an Availability Zone", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/v2.0/lbaas/availabilityzones/{availability_zone_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone:get_one", + check_str=("rule:load-balancer:read"), + description="Show Availability Zone details", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v2.0/lbaas/availabilityzones/{availability_zone_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone:delete", + check_str=("rule:load-balancer:admin"), + description="Remove an Availability Zone", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/v2.0/lbaas/availabilityzones/{availability_zone_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone-profile:get_all", + check_str=("rule:load-balancer:admin"), + description="List Availability Zones", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2.0/lbaas/availabilityzoneprofiles"}], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone-profile:post", + check_str=("rule:load-balancer:admin"), + description="Create an Availability Zone", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2.0/lbaas/availabilityzoneprofiles"}], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone-profile:put", + check_str=("rule:load-balancer:admin"), + description="Update an Availability Zone", + scope_types=["project"], + operations=[ + { + "method": "PUT", + "path": "/v2.0/lbaas/availabilityzoneprofiles/{availability_zone_profile_id}", + }, + ], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone-profile:get_one", + check_str=("rule:load-balancer:admin"), + description="Show Availability Zone details", + scope_types=["project"], + operations=[ + { + "method": "GET", + "path": "/v2.0/lbaas/availabilityzoneprofiles/{availability_zone_profile_id}", + }, + ], + ), + base.APIRule( + name="os_load-balancer_api:availability-zone-profile:delete", + check_str=("rule:load-balancer:admin"), + description="Remove an Availability Zone", + scope_types=["project"], + operations=[ + { + "method": "DELETE", + "path": "/v2.0/lbaas/availabilityzoneprofiles/{availability_zone_profile_id}", + }, + ], + ), + base.APIRule( + name="os_load-balancer_api:healthmonitor:get_all", + check_str=("rule:load-balancer:read"), + description="List Health Monitors of a Pool", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/healthmonitors"}], + ), + base.APIRule( + name="os_load-balancer_api:healthmonitor:get_all-global", + check_str=("rule:load-balancer:read-global"), + description="List Health Monitors including resources owned by others", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/healthmonitors"}], + ), + base.APIRule( + name="os_load-balancer_api:healthmonitor:post", + check_str=("rule:load-balancer:write"), + description="Create a Health Monitor", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2/lbaas/healthmonitors"}], + ), + base.APIRule( + name="os_load-balancer_api:healthmonitor:get_one", + check_str=("rule:load-balancer:read"), + description="Show Health Monitor details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/healthmonitors/{healthmonitor_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:healthmonitor:put", + check_str=("rule:load-balancer:write"), + description="Update a Health Monitor", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/lbaas/healthmonitors/{healthmonitor_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:healthmonitor:delete", + check_str=("rule:load-balancer:write"), + description="Remove a Health Monitor", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2/lbaas/healthmonitors/{healthmonitor_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:l7policy:get_all", + check_str=("rule:load-balancer:read"), + description="List L7 Policys", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/l7policies"}], + ), + base.APIRule( + name="os_load-balancer_api:l7policy:get_all-global", + check_str=("rule:load-balancer:read-global"), + description="List L7 Policys including resources owned by others", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/l7policies"}], + ), + base.APIRule( + name="os_load-balancer_api:l7policy:post", + check_str=("rule:load-balancer:write"), + description="Create a L7 Policy", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2/lbaas/l7policies"}], + ), + base.APIRule( + name="os_load-balancer_api:l7policy:get_one", + check_str=("rule:load-balancer:read"), + description="Show L7 Policy details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/l7policies/{l7policy_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:l7policy:put", + check_str=("rule:load-balancer:write"), + description="Update a L7 Policy", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/lbaas/l7policies/{l7policy_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:l7policy:delete", + check_str=("rule:load-balancer:write"), + description="Remove a L7 Policy", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2/lbaas/l7policies/{l7policy_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:l7rule:get_all", + check_str=("rule:load-balancer:read"), + description="List L7 Rules", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/l7policies/{l7policy_id}/rules"}], + ), + base.APIRule( + name="os_load-balancer_api:l7rule:post", + check_str=("rule:load-balancer:write"), + description="Create a L7 Rule", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2/lbaas/l7policies/{l7policy_id}/rules"}], + ), + base.APIRule( + name="os_load-balancer_api:l7rule:get_one", + check_str=("rule:load-balancer:read"), + description="Show L7 Rule details", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v2/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:l7rule:put", + check_str=("rule:load-balancer:write"), + description="Update a L7 Rule", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/v2/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:l7rule:delete", + check_str=("rule:load-balancer:write"), + description="Remove a L7 Rule", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/v2/lbaas/l7policies/{l7policy_id}/rules/{l7rule_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:listener:get_all", + check_str=("rule:load-balancer:read"), + description="List Listeners", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/listeners"}], + ), + base.APIRule( + name="os_load-balancer_api:listener:get_all-global", + check_str=("rule:load-balancer:read-global"), + description="List Listeners including resources owned by others", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/listeners"}], + ), + base.APIRule( + name="os_load-balancer_api:listener:post", + check_str=("rule:load-balancer:write"), + description="Create a Listener", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2/lbaas/listeners"}], + ), + base.APIRule( + name="os_load-balancer_api:listener:get_one", + check_str=("rule:load-balancer:read"), + description="Show Listener details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/listeners/{listener_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:listener:put", + check_str=("rule:load-balancer:write"), + description="Update a Listener", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/lbaas/listeners/{listener_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:listener:delete", + check_str=("rule:load-balancer:write"), + description="Remove a Listener", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2/lbaas/listeners/{listener_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:listener:get_stats", + check_str=("rule:load-balancer:read"), + description="Show Listener statistics", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/listeners/{listener_id}/stats"}], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:get_all", + check_str=("rule:load-balancer:read"), + description="List Load Balancers", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/loadbalancers"}], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:get_all-global", + check_str=("rule:load-balancer:read-global"), + description="List Load Balancers including resources owned by others", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/loadbalancers"}], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:post", + check_str=("rule:load-balancer:write"), + description="Create a Load Balancer", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2/lbaas/loadbalancers"}], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:get_one", + check_str=("rule:load-balancer:read"), + description="Show Load Balancer details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/loadbalancers/{loadbalancer_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:put", + check_str=("rule:load-balancer:write"), + description="Update a Load Balancer", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/lbaas/loadbalancers/{loadbalancer_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:delete", + check_str=("rule:load-balancer:write"), + description="Remove a Load Balancer", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2/lbaas/loadbalancers/{loadbalancer_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:get_stats", + check_str=("rule:load-balancer:read"), + description="Show Load Balancer statistics", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v2/lbaas/loadbalancers/{loadbalancer_id}/stats"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:get_status", + check_str=("rule:load-balancer:read"), + description="Show Load Balancer status", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v2/lbaas/loadbalancers/{loadbalancer_id}/status"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:loadbalancer:put_failover", + check_str=("rule:load-balancer:admin"), + description="Failover a Load Balancer", + scope_types=["project"], + operations=[ + {"method": "PUT", "path": "/v2/lbaas/loadbalancers/{loadbalancer_id}/failover"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:member:get_all", + check_str=("rule:load-balancer:read"), + description="List Members of a Pool", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/pools/{pool_id}/members"}], + ), + base.APIRule( + name="os_load-balancer_api:member:post", + check_str=("rule:load-balancer:write"), + description="Create a Member", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2/lbaas/pools/{pool_id}/members"}], + ), + base.APIRule( + name="os_load-balancer_api:member:get_one", + check_str=("rule:load-balancer:read"), + description="Show Member details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/pools/{pool_id}/members/{member_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:member:put", + check_str=("rule:load-balancer:write"), + description="Update a Member", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/lbaas/pools/{pool_id}/members/{member_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:member:delete", + check_str=("rule:load-balancer:write"), + description="Remove a Member", + scope_types=["project"], + operations=[ + {"method": "DELETE", "path": "/v2/lbaas/pools/{pool_id}/members/{member_id}"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:pool:get_all", + check_str=("rule:load-balancer:read"), + description="List Pools", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/pools"}], + ), + base.APIRule( + name="os_load-balancer_api:pool:get_all-global", + check_str=("rule:load-balancer:read-global"), + description="List Pools including resources owned by others", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/pools"}], + ), + base.APIRule( + name="os_load-balancer_api:pool:post", + check_str=("rule:load-balancer:write"), + description="Create a Pool", + scope_types=["project"], + operations=[{"method": "POST", "path": "/v2/lbaas/pools"}], + ), + base.APIRule( + name="os_load-balancer_api:pool:get_one", + check_str=("rule:load-balancer:read"), + description="Show Pool details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/pools/{pool_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:pool:put", + check_str=("rule:load-balancer:write"), + description="Update a Pool", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/lbaas/pools/{pool_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:pool:delete", + check_str=("rule:load-balancer:write"), + description="Remove a Pool", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2/lbaas/pools/{pool_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:provider:get_all", + check_str=("rule:load-balancer:read"), + description="List enabled providers", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/providers"}], + ), + base.APIRule( + name="os_load-balancer_api:quota:get_all", + check_str=("rule:load-balancer:read-quota"), + description="List Quotas", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/quotas"}], + ), + base.APIRule( + name="os_load-balancer_api:quota:get_all-global", + check_str=("rule:load-balancer:read-quota-global"), + description="List Quotas including resources owned by others", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/quotas"}], + ), + base.APIRule( + name="os_load-balancer_api:quota:get_one", + check_str=("rule:load-balancer:read-quota"), + description="Show Quota details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/quotas/{project_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:quota:put", + check_str=("rule:load-balancer:write-quota"), + description="Update a Quota", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/lbaas/quotas/{project_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:quota:delete", + check_str=("rule:load-balancer:write-quota"), + description="Reset a Quota", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2/lbaas/quotas/{project_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:quota:get_defaults", + check_str=("rule:load-balancer:read-quota"), + description="Show Default Quota for a Project", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/lbaas/quotas/{project_id}/default"}], + ), + base.APIRule( + name="os_load-balancer_api:amphora:get_all", + check_str=("rule:load-balancer:admin"), + description="List Amphorae", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/octavia/amphorae"}], + ), + base.APIRule( + name="os_load-balancer_api:amphora:get_one", + check_str=("rule:load-balancer:admin"), + description="Show Amphora details", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/octavia/amphorae/{amphora_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:amphora:delete", + check_str=("rule:load-balancer:admin"), + description="Delete an Amphora", + scope_types=["project"], + operations=[{"method": "DELETE", "path": "/v2/octavia/amphorae/{amphora_id}"}], + ), + base.APIRule( + name="os_load-balancer_api:amphora:put_config", + check_str=("rule:load-balancer:admin"), + description="Update Amphora Agent Configuration", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/octavia/amphorae/{amphora_id}/config"}], + ), + base.APIRule( + name="os_load-balancer_api:amphora:put_failover", + check_str=("rule:load-balancer:admin"), + description="Failover Amphora", + scope_types=["project"], + operations=[{"method": "PUT", "path": "/v2/octavia/amphorae/{amphora_id}/failover"}], + ), + base.APIRule( + name="os_load-balancer_api:amphora:get_stats", + check_str=("rule:load-balancer:admin"), + description="Show Amphora statistics", + scope_types=["project"], + operations=[{"method": "GET", "path": "/v2/octavia/amphorae/{amphora_id}/stats"}], + ), + base.APIRule( + name="os_load-balancer_api:provider-flavor:get_all", + check_str=("rule:load-balancer:admin"), + description="List the provider flavor capabilities.", + scope_types=["project"], + operations=[ + {"method": "GET", "path": "/v2/lbaas/providers/{provider}/flavor_capabilities"}, + ], + ), + base.APIRule( + name="os_load-balancer_api:provider-availability-zone:get_all", + check_str=("rule:load-balancer:admin"), + description="List the provider availability zone capabilities.", + scope_types=["project"], + operations=[ + { + "method": "GET", + "path": "/v2/lbaas/providers/{provider}/availability_zone_capabilities", + }, + ], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/panko.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/panko.py new file mode 100644 index 0000000..f00e4fb --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/panko.py @@ -0,0 +1,35 @@ +from . import base + +list_rules = ( + base.Rule( + name="context_is_admin", + check_str=("role:admin"), + description="No description", + ), + base.APIRule( + name="segregation", + check_str=("role:admin and system_scope:all"), + description="Return the user and project the requestshould be limited to", + scope_types=["system"], + operations=[ + {"method": "GET", "path": "/v2/events"}, + {"method": "GET", "path": "/v2/events/{message_id}"}, + ], + ), + base.APIRule( + name="telemetry:events:index", + check_str=(""), + description="Return all events matching the query filters.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/events"}], + ), + base.APIRule( + name="telemetry:events:show", + check_str=(""), + description="Return a single event with the given message id.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/v2/events/{message_id}"}], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/policies/placement.py b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/placement.py new file mode 100644 index 0000000..db71b8d --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/policies/placement.py @@ -0,0 +1,261 @@ +from . import base + +list_rules = ( + base.Rule( + name="admin_api", + check_str=("role:admin"), + description="Default rule for most placement APIs.", + ), + base.APIRule( + name="placement:resource_providers:list", + check_str=("role:reader and system_scope:all"), + description="List resource providers.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_providers"}], + ), + base.APIRule( + name="placement:resource_providers:create", + check_str=("role:admin and system_scope:all"), + description="Create resource provider.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/resource_providers"}], + ), + base.APIRule( + name="placement:resource_providers:show", + check_str=("role:reader and system_scope:all"), + description="Show resource provider.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_providers/{uuid}"}], + ), + base.APIRule( + name="placement:resource_providers:update", + check_str=("role:admin and system_scope:all"), + description="Update resource provider.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/resource_providers/{uuid}"}], + ), + base.APIRule( + name="placement:resource_providers:delete", + check_str=("role:admin and system_scope:all"), + description="Delete resource provider.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/resource_providers/{uuid}"}], + ), + base.APIRule( + name="placement:resource_classes:list", + check_str=("role:reader and system_scope:all"), + description="List resource classes.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_classes"}], + ), + base.APIRule( + name="placement:resource_classes:create", + check_str=("role:admin and system_scope:all"), + description="Create resource class.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/resource_classes"}], + ), + base.APIRule( + name="placement:resource_classes:show", + check_str=("role:reader and system_scope:all"), + description="Show resource class.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_classes/{name}"}], + ), + base.APIRule( + name="placement:resource_classes:update", + check_str=("role:admin and system_scope:all"), + description="Update resource class.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/resource_classes/{name}"}], + ), + base.APIRule( + name="placement:resource_classes:delete", + check_str=("role:admin and system_scope:all"), + description="Delete resource class.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/resource_classes/{name}"}], + ), + base.APIRule( + name="placement:resource_providers:inventories:list", + check_str=("role:reader and system_scope:all"), + description="List resource provider inventories.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_providers/{uuid}/inventories"}], + ), + base.APIRule( + name="placement:resource_providers:inventories:create", + check_str=("role:admin and system_scope:all"), + description="Create one resource provider inventory.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/resource_providers/{uuid}/inventories"}], + ), + base.APIRule( + name="placement:resource_providers:inventories:show", + check_str=("role:reader and system_scope:all"), + description="Show resource provider inventory.", + scope_types=["system"], + operations=[ + { + "method": "GET", + "path": "/resource_providers/{uuid}/inventories/{resource_class}", + }, + ], + ), + base.APIRule( + name="placement:resource_providers:inventories:update", + check_str=("role:admin and system_scope:all"), + description="Update resource provider inventory.", + scope_types=["system"], + operations=[ + {"method": "PUT", "path": "/resource_providers/{uuid}/inventories"}, + { + "method": "PUT", + "path": "/resource_providers/{uuid}/inventories/{resource_class}", + }, + ], + ), + base.APIRule( + name="placement:resource_providers:inventories:delete", + check_str=("role:admin and system_scope:all"), + description="Delete resource provider inventory.", + scope_types=["system"], + operations=[ + {"method": "DELETE", "path": "/resource_providers/{uuid}/inventories"}, + { + "method": "DELETE", + "path": "/resource_providers/{uuid}/inventories/{resource_class}", + }, + ], + ), + base.APIRule( + name="placement:resource_providers:aggregates:list", + check_str=("role:reader and system_scope:all"), + description="List resource provider aggregates.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_providers/{uuid}/aggregates"}], + ), + base.APIRule( + name="placement:resource_providers:aggregates:update", + check_str=("role:admin and system_scope:all"), + description="Update resource provider aggregates.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/resource_providers/{uuid}/aggregates"}], + ), + base.APIRule( + name="placement:resource_providers:usages", + check_str=("role:reader and system_scope:all"), + description="List resource provider usages.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_providers/{uuid}/usages"}], + ), + base.APIRule( + name="placement:usages", + check_str=( + "(role:reader and system_scope:all) or (role:reader and project_id:%(project_id)s)" + ), + description="List total resource usages for a given project.", + scope_types=["system", "project"], + operations=[{"method": "GET", "path": "/usages"}], + ), + base.APIRule( + name="placement:traits:list", + check_str=("role:reader and system_scope:all"), + description="List traits.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/traits"}], + ), + base.APIRule( + name="placement:traits:show", + check_str=("role:reader and system_scope:all"), + description="Show trait.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/traits/{name}"}], + ), + base.APIRule( + name="placement:traits:update", + check_str=("role:admin and system_scope:all"), + description="Update trait.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/traits/{name}"}], + ), + base.APIRule( + name="placement:traits:delete", + check_str=("role:admin and system_scope:all"), + description="Delete trait.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/traits/{name}"}], + ), + base.APIRule( + name="placement:resource_providers:traits:list", + check_str=("role:reader and system_scope:all"), + description="List resource provider traits.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_providers/{uuid}/traits"}], + ), + base.APIRule( + name="placement:resource_providers:traits:update", + check_str=("role:admin and system_scope:all"), + description="Update resource provider traits.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/resource_providers/{uuid}/traits"}], + ), + base.APIRule( + name="placement:resource_providers:traits:delete", + check_str=("role:admin and system_scope:all"), + description="Delete resource provider traits.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/resource_providers/{uuid}/traits"}], + ), + base.APIRule( + name="placement:allocations:manage", + check_str=("role:admin and system_scope:all"), + description="Manage allocations.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/allocations"}], + ), + base.APIRule( + name="placement:allocations:list", + check_str=("role:reader and system_scope:all"), + description="List allocations.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/allocations/{consumer_uuid}"}], + ), + base.APIRule( + name="placement:allocations:update", + check_str=("role:admin and system_scope:all"), + description="Update allocations.", + scope_types=["system"], + operations=[{"method": "PUT", "path": "/allocations/{consumer_uuid}"}], + ), + base.APIRule( + name="placement:allocations:delete", + check_str=("role:admin and system_scope:all"), + description="Delete allocations.", + scope_types=["system"], + operations=[{"method": "DELETE", "path": "/allocations/{consumer_uuid}"}], + ), + base.APIRule( + name="placement:resource_providers:allocations:list", + check_str=("role:reader and system_scope:all"), + description="List resource provider allocations.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/resource_providers/{uuid}/allocations"}], + ), + base.APIRule( + name="placement:allocation_candidates:list", + check_str=("role:reader and system_scope:all"), + description="List allocation candidates.", + scope_types=["system"], + operations=[{"method": "GET", "path": "/allocation_candidates"}], + ), + base.APIRule( + name="placement:reshaper:reshape", + check_str=("role:admin and system_scope:all"), + description="Reshape Inventory and Allocations.", + scope_types=["system"], + operations=[{"method": "POST", "path": "/reshaper"}], + ), +) + +__all__ = ("list_rules",) diff --git a/libs/skyline-policy-manager/src/skyline_policy_manager/schema.py b/libs/skyline-policy-manager/src/skyline_policy_manager/schema.py new file mode 100644 index 0000000..961ac8d --- /dev/null +++ b/libs/skyline-policy-manager/src/skyline_policy_manager/schema.py @@ -0,0 +1,56 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from enum import Enum +from typing import List, TypedDict + +from pydantic import BaseModel + + +class ScopeType(str, Enum): + system = "system" + domain = "domain" + project = "project" + + +class ScopeTypesSchema(BaseModel): + __root__: List[ScopeType] + + +class Method(str, Enum): + GET = "GET" + POST = "POST" + PUT = "PUT" + PATCH = "PATCH" + DELETE = "DELETE" + HEAD = "HEAD" + + +class Operation(TypedDict): + method: str + path: str + + +class OperationSchema(BaseModel): + method: Method + path: str + + +class OperationsSchema(BaseModel): + __root__: List[OperationSchema] + + +__all__ = ("ScopeType", "ScopeTypesSchema", "Method", "Operation", "OperationsSchema") diff --git a/libs/skyline-policy-manager/tests/__init__.py b/libs/skyline-policy-manager/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/skyline-policy-manager/tests/test_skyline_policy_manager.py b/libs/skyline-policy-manager/tests/test_skyline_policy_manager.py new file mode 100644 index 0000000..7a70a4f --- /dev/null +++ b/libs/skyline-policy-manager/tests/test_skyline_policy_manager.py @@ -0,0 +1,19 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from skyline_policy_manager import __version__ + + +def test_version(): + assert __version__ == "0.1.0" diff --git a/libs/skyline-policy-manager/tools/post_install.sh b/libs/skyline-policy-manager/tools/post_install.sh new file mode 100755 index 0000000..d6aee92 --- /dev/null +++ b/libs/skyline-policy-manager/tools/post_install.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Install openstack service package +poetry run pip install --no-deps \ +keystone \ +openstack-placement \ +nova \ +cinder \ +glance \ +neutron neutron-vpnaas \ +openstack-heat \ +ironic-lib ironic ironic-inspector \ +octavia-lib octavia \ +panko + +# Patch cinder +patch_path="$(poetry run python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')/cinder/__init__.py" +sed -i 's/\(.*eventlet.*\)/# \1/g' $patch_path + +# Patch neutron +patch_path="$(poetry run python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')/neutron/conf/policies/floatingip_pools.py" +sed -i 's/admin/system/g' $patch_path + +# Patch ironic +patch_path="$(poetry run python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')/ironic/common/policy.py" +sed -i 's/\(.*lockutils.*\)/# \1/g' $patch_path + +# Patch ironic_inspector +patch_path="$(poetry run python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')/ironic_inspector/policy.py" +sed -i 's/\(.*lockutils.*\)/# \1/g' $patch_path diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..fe3fe7e --- /dev/null +++ b/mypy.ini @@ -0,0 +1,37 @@ +[mypy] +show_error_codes = true +show_error_context = true + +; check_untyped_defs = true +; disallow_incomplete_defs = true +; disallow_untyped_calls = true +; disallow_untyped_decorators = true +; disallow_untyped_defs = true +; ignore_missing_imports = true +; incremental = false +; no_implicit_optional = true +; pretty = true +; raise_exceptions = true +; strict_equality = true +; warn_incomplete_stub = true +; warn_redundant_casts = true +; warn_return_any = true +; warn_unreachable = true +; warn_unused_configs = true +; warn_unused_ignores = true +; allow_redefinition = true +; implicit_reexport = true + +; # NOTE: Maybe need remove +; disallow_subclassing_any = true +; disallow_any_decorated = true +; disallow_any_explicit = false +; disallow_any_expr = false +; disallow_any_generics = true +; disallow_any_unimported = false + +[pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..3d1df63 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,3087 @@ +[[package]] +name = "add-trailing-comma" +version = "2.1.0" +description = "Automatically add trailing commas to calls and literals" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +tokenize-rt = ">=3.0.1" + +[[package]] +name = "aiofiles" +version = "0.7.0" +description = "File support for asyncio." +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "aiomysql" +version = "0.0.21" +description = "MySQL driver for asyncio." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +PyMySQL = ">=0.9,<=0.9.3" + +[package.extras] +sa = ["sqlalchemy (>=1.0)"] + +[[package]] +name = "aiosqlite" +version = "0.17.0" +description = "asyncio bridge to the standard sqlite3 module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing_extensions = ">=3.7.2" + +[[package]] +name = "alembic" +version = "1.6.2" +description = "A database migration tool for SQLAlchemy." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +Mako = "*" +python-dateutil = "*" +python-editor = ">=0.3" +SQLAlchemy = ">=1.3.0" + +[[package]] +name = "aniso8601" +version = "7.0.0" +description = "A library for parsing ISO 8601 strings." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "apipkg" +version = "1.5" +description = "apipkg: namespace control and lazy-import mechanism" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "asgi-lifespan" +version = "1.0.1" +description = "Programmatic startup/shutdown of ASGI apps." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +sniffio = "*" + +[[package]] +name = "async-exit-stack" +version = "1.0.1" +description = "AsyncExitStack backport for Python 3.5+" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "async-generator" +version = "1.10" +description = "Async generators and context managers for Python 3.5+" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "babel" +version = "2.9.1" +description = "Internationalization utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "bandit" +version = "1.7.0" +description = "Security oriented static analyser for python code." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +GitPython = ">=1.0.1" +PyYAML = ">=5.3.1" +six = ">=1.10.0" +stevedore = ">=1.20.0" + +[[package]] +name = "bcrypt" +version = "3.2.0" +description = "Modern password hashing for your software and your servers" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.1" +six = ">=1.4.1" + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.3.0" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "cliff" +version = "3.7.0" +description = "Command Line Interface Formulation Framework" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cmd2 = ">=1.0.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PrettyTable = ">=0.7.2" +pyparsing = ">=2.1.0" +PyYAML = ">=3.12" +stevedore = ">=2.0.1" + +[[package]] +name = "cmd2" +version = "1.5.0" +description = "cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +attrs = ">=16.3.0" +colorama = ">=0.3.7" +pyperclip = ">=1.6" +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} +wcwidth = ">=0.1.7" + +[package.extras] +dev = ["pytest (>=4.6)", "codecov", "pytest-cov", "pytest-mock", "flake8", "sphinx", "sphinx-rtd-theme", "sphinx-autobuild", "doc8", "invoke", "twine (>=1.11)", "mock", "nox (==2019.11.9)", "nox"] +test = ["codecov", "coverage", "pytest (>=4.6)", "pytest-cov", "pytest-mock", "mock", "gnureadline"] + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "cryptography" +version = "3.4.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] +name = "databases" +version = "0.4.3" +description = "Async database support for Python." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +sqlalchemy = "<1.4" + +[package.extras] +mysql = ["aiomysql"] +postgresql = ["asyncpg"] +postgresql_aiopg = ["aiopg"] +sqlite = ["aiosqlite"] + +[[package]] +name = "debtcollector" +version = "2.2.0" +description = "A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +six = ">=1.10.0" +wrapt = ">=1.7.0" + +[[package]] +name = "decorator" +version = "5.0.9" +description = "Decorators for Humans" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "distlib" +version = "0.3.1" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "dnspython" +version = "2.1.0" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dnssec = ["cryptography (>=2.6)"] +doh = ["requests", "requests-toolbelt"] +idna = ["idna (>=2.1)"] +curio = ["curio (>=1.2)", "sniffio (>=1.1)"] +trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] + +[[package]] +name = "dogpile.cache" +version = "1.1.2" +description = "A caching front-end based on the Dogpile lock." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +decorator = ">=4.0.0" +stevedore = ">=3.0.0" + +[[package]] +name = "ecdsa" +version = "0.14.1" +description = "ECDSA cryptographic signature library (pure python)" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[package.dependencies] +six = "*" + +[[package]] +name = "email-validator" +version = "1.1.2" +description = "A robust email syntax and deliverability validation library for Python 2.x/3.x." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +dnspython = ">=1.15.0" +idna = ">=2.0.0" + +[[package]] +name = "execnet" +version = "1.8.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +apipkg = ">=1.4" + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "fastapi" +version = "0.58.1" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiofiles = {version = "*", optional = true, markers = "extra == \"all\""} +async_exit_stack = {version = "*", optional = true, markers = "extra == \"all\""} +async_generator = {version = "*", optional = true, markers = "extra == \"all\""} +email_validator = {version = "*", optional = true, markers = "extra == \"all\""} +graphene = {version = "*", optional = true, markers = "extra == \"all\""} +itsdangerous = {version = "*", optional = true, markers = "extra == \"all\""} +jinja2 = {version = "*", optional = true, markers = "extra == \"all\""} +orjson = {version = "*", optional = true, markers = "extra == \"all\""} +pydantic = ">=0.32.2,<2.0.0" +python-multipart = {version = "*", optional = true, markers = "extra == \"all\""} +pyyaml = {version = "*", optional = true, markers = "extra == \"all\""} +requests = {version = "*", optional = true, markers = "extra == \"all\""} +starlette = "0.13.4" +ujson = {version = "*", optional = true, markers = "extra == \"all\""} +uvicorn = {version = "*", optional = true, markers = "extra == \"all\""} + +[package.extras] +all = ["requests", "aiofiles", "jinja2", "python-multipart", "itsdangerous", "pyyaml", "graphene", "ujson", "orjson", "email-validator", "uvicorn", "async-exit-stack", "async-generator"] +dev = ["pyjwt", "passlib", "autoflake", "flake8", "uvicorn", "graphene"] +doc = ["mkdocs", "mkdocs-material", "markdown-include", "typer", "typer-cli", "pyyaml"] +test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "mypy", "black", "isort", "requests", "email-validator", "sqlalchemy", "peewee", "databases", "orjson", "async-exit-stack", "async-generator", "python-multipart", "aiofiles", "flask"] + +[[package]] +name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "gitdb" +version = "4.0.7" +description = "Git Object Database" +category = "dev" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +smmap = ">=3.0.1,<5" + +[[package]] +name = "gitpython" +version = "3.1.17" +description = "Python Git Library" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[[package]] +name = "graphene" +version = "2.1.8" +description = "GraphQL Framework for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +aniso8601 = ">=3,<=7" +graphql-core = ">=2.1,<3" +graphql-relay = ">=2,<3" +six = ">=1.10.0,<2" + +[package.extras] +django = ["graphene-django"] +sqlalchemy = ["graphene-sqlalchemy"] +test = ["pytest", "pytest-benchmark", "pytest-cov", "pytest-mock", "snapshottest", "coveralls", "promise", "six", "mock", "pytz", "iso8601"] + +[[package]] +name = "graphql-core" +version = "2.3.2" +description = "GraphQL implementation for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +promise = ">=2.3,<3" +rx = ">=1.6,<2" +six = ">=1.10.0" + +[package.extras] +gevent = ["gevent (>=1.1)"] +test = ["six (==1.14.0)", "pyannotate (==1.2.0)", "pytest (==4.6.10)", "pytest-django (==3.9.0)", "pytest-cov (==2.8.1)", "coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pytest-benchmark (==3.2.3)", "pytest-mock (==2.0.0)"] + +[[package]] +name = "graphql-relay" +version = "2.0.1" +description = "Relay implementation for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +graphql-core = ">=2.2,<3" +promise = ">=2.2,<3" +six = ">=1.12" + +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "hiyapyco" +version = "0.4.16" +description = "Hierarchical Yaml Python Config" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Jinja2 = ">1,<3" +PyYAML = "*" + +[[package]] +name = "httpcore" +version = "0.12.3" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +h11 = "<1.0.0" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] + +[[package]] +name = "httptools" +version = "0.1.2" +description = "A collection of framework independent HTTP protocol utils." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["Cython (==0.29.22)"] + +[[package]] +name = "httpx" +version = "0.16.1" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.12.0,<0.13.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotlipy (>=0.7.0,<0.8.0)"] +http2 = ["h2 (>=3.0.0,<4.0.0)"] + +[[package]] +name = "identify" +version = "2.2.4" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.extras] +license = ["editdistance-s"] + +[[package]] +name = "idna" +version = "3.1" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.4" + +[[package]] +name = "immutables" +version = "0.15" +description = "Immutable Collections" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +test = ["flake8 (>=3.8.4,<3.9.0)", "pycodestyle (>=2.6.0,<2.7.0)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "iso8601" +version = "0.1.14" +description = "Simple module to parse ISO 8601 dates" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.8.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "itsdangerous" +version = "2.0.1" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "jinja2" +version = "2.11.3" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +name = "jmespath" +version = "0.10.0" +description = "JSON Matching Expressions" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "jsonpatch" +version = "1.32" +description = "Apply JSON-Patches (RFC 6902)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.1" +description = "Identify specific nodes in a JSON document (RFC 6901)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "jsonschema" +version = "3.2.0" +description = "An implementation of JSON Schema validation for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0" +six = ">=1.11.0" + +[package.extras] +format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"] + +[[package]] +name = "keystoneauth1" +version = "3.17.3" +description = "Authentication Library for OpenStack Identity" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +iso8601 = ">=0.1.11" +os-service-types = ">=1.2.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +requests = ">=2.14.2" +six = ">=1.10.0" +stevedore = ">=1.20.0" + +[package.extras] +betamax = ["betamax (>=0.7.0)", "fixtures (>=3.0.0)", "mock (>=2.0.0)"] +kerberos = ["requests-kerberos (>=0.8.0)"] +oauth1 = ["oauthlib (>=0.6.2)"] +saml2 = ["lxml (>=3.4.1,!=3.7.0)"] +test = ["PyYAML (>=3.12)", "bandit (>=1.1.0,<1.6.0)", "betamax (>=0.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "flake8-docstrings (==0.2.1.post1)", "flake8-import-order (>=0.17.1)", "hacking (>=0.12.0,!=0.13.0,<0.14)", "lxml (>=3.4.1,!=3.7.0)", "mock (>=2.0.0)", "oauthlib (>=0.6.2)", "openstackdocstheme (>=1.18.1)", "oslo.config (>=5.2.0)", "oslo.utils (>=3.33.0)", "oslotest (>=3.2.0)", "reno (>=2.5.0)", "requests-kerberos (>=0.8.0)", "requests-mock (>=1.2.0)", "stestr (>=1.0.0)", "testresources (>=2.0.0)", "testtools (>=2.2.0)"] + +[[package]] +name = "loguru" +version = "0.5.3" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] + +[[package]] +name = "mako" +version = "1.1.4" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "msgpack" +version = "1.0.2" +description = "MessagePack (de)serializer." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "munch" +version = "2.5.0" +description = "A dot-accessible dictionary (a la JavaScript objects)" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[package.extras] +testing = ["pytest", "coverage", "astroid (>=1.5.3,<1.6.0)", "pylint (>=1.7.2,<1.8.0)", "astroid (>=2.0)", "pylint (>=2.3.1,<2.4.0)"] +yaml = ["PyYAML (>=5.1.0)"] + +[[package]] +name = "mypy" +version = "0.812" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "netaddr" +version = "0.8.0" +description = "A network address manipulation library for Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "netifaces" +version = "0.10.9" +description = "Portable network interface information." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "openstacksdk" +version = "0.36.5" +description = "An SDK for building applications to work with OpenStack" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +appdirs = ">=1.3.0" +cryptography = ">=2.1" +decorator = ">=3.4.0" +"dogpile.cache" = ">=0.6.2" +iso8601 = ">=0.1.11" +jmespath = ">=0.9.0" +jsonpatch = ">=1.16,<1.20 || >1.20" +keystoneauth1 = ">=3.16.0" +munch = ">=2.1.0" +netifaces = ">=0.10.4" +os-service-types = ">=1.7.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PyYAML = ">=3.12" +requestsexceptions = ">=1.2.0" +six = ">=1.10.0" + +[[package]] +name = "orjson" +version = "3.5.2" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "os-client-config" +version = "2.1.0" +description = "OpenStack Client Configuation Library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +openstacksdk = ">=0.13.0" + +[[package]] +name = "os-service-types" +version = "1.7.0" +description = "Python library for consuming OpenStack sevice-types-authority data" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "osc-lib" +version = "2.4.0" +description = "OpenStackClient Library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cliff = ">=3.2.0" +keystoneauth1 = ">=3.14.0" +openstacksdk = ">=0.15.0" +"oslo.i18n" = ">=3.15.3" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +simplejson = ">=3.5.1" +stevedore = ">=1.20.0" + +[[package]] +name = "osc-placement" +version = "1.7.0" +description = "OpenStackClient plugin for the Placement service" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +keystoneauth1 = ">=3.3.0" +osc-lib = ">=1.2.0" +"oslo.utils" = ">=3.37.0" +pbr = ">=2.0.0" +simplejson = ">=3.16.0" +six = ">=1.10.0" + +[[package]] +name = "oslo.config" +version = "6.3.0" +description = "Oslo Configuration API" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +debtcollector = ">=1.2.0" +netaddr = ">=0.7.18" +"oslo.i18n" = ">=3.15.3" +PyYAML = ">=3.12" +rfc3986 = ">=0.3.1" +six = ">=1.10.0" +stevedore = ">=1.20.0" + +[[package]] +name = "oslo.context" +version = "3.2.0" +description = "Oslo Context library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.2.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "oslo.i18n" +version = "5.0.1" +description = "Oslo i18n library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +six = ">=1.10.0" + +[[package]] +name = "oslo.log" +version = "4.5.0" +description = "oslo.log library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.19.0" +"oslo.config" = ">=5.2.0" +"oslo.context" = ">=2.20.0" +"oslo.i18n" = ">=3.20.0" +"oslo.serialization" = ">=2.25.0" +"oslo.utils" = ">=3.36.0" +pbr = ">=3.1.1" +pyinotify = {version = ">=0.9.6", markers = "sys_platform != \"win32\" and sys_platform != \"darwin\" and sys_platform != \"sunos5\""} +python-dateutil = ">=2.7.0" + +[package.extras] +fixtures = ["fixtures (>=3.0.0)"] +systemd = ["systemd-python (>=234)"] +test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.5.1)", "fixtures (>=3.0.0)", "hacking (>=2.0.0,<2.1.0)", "oslotest (>=3.3.0)", "pre-commit (>=2.6.0)", "stestr (>=2.0.0)", "testtools (>=2.3.0)"] + +[[package]] +name = "oslo.policy" +version = "3.8.0" +description = "Oslo Policy library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +"oslo.config" = ">=6.0.0" +"oslo.context" = ">=2.22.0" +"oslo.i18n" = ">=3.15.3" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.40.0" +PyYAML = ">=5.1" +requests = ">=2.14.2" +stevedore = ">=1.20.0" + +[[package]] +name = "oslo.serialization" +version = "4.1.0" +description = "Oslo Serialization library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +msgpack = ">=0.5.2" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +pytz = ">=2013.6" + +[[package]] +name = "oslo.utils" +version = "4.9.0" +description = "Oslo Utility library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +debtcollector = ">=1.2.0" +iso8601 = ">=0.1.11" +netaddr = ">=0.7.18" +netifaces = ">=0.10.4" +"oslo.i18n" = ">=3.15.3" +packaging = ">=20.4" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +pyparsing = ">=2.1.0" +pytz = ">=2013.6" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] +totp = ["cryptography"] + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pbr" +version = "5.6.0" +description = "Python Build Reasonableness" +category = "main" +optional = false +python-versions = ">=2.6" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "pre-commit" +version = "2.12.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "prettytable" +version = "0.7.2" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "promise" +version = "2.3" +description = "Promises/A+ implementation for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[package.extras] +test = ["pytest (>=2.7.3)", "pytest-cov", "coveralls", "futures", "pytest-benchmark", "mock"] + +[[package]] +name = "psutil" +version = "5.8.0" +description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyinotify" +version = "0.9.6" +description = "Linux filesystem events monitoring" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pympler" +version = "0.9" +description = "A development tool to measure, monitor and analyze the memory behavior of Python objects." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pymysql" +version = "0.9.3" +description = "Pure Python MySQL Driver" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +rsa = ["cryptography"] + +[[package]] +name = "pyopenssl" +version = "20.0.1" +description = "Python wrapper module around the OpenSSL library" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.dependencies] +cryptography = ">=3.2" +six = ">=1.5.2" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pyperclip" +version = "1.8.2" +description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyperf" +version = "2.2.0" +description = "Python module to run and analyze benchmarks" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyreadline3" +version = "3.3" +description = "A python implementation of GNU readline." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyrsistent" +version = "0.17.3" +description = "Persistent/Functional/Immutable data structures" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "pytest" +version = "6.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +checkqa_mypy = ["mypy (==0.780)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.15.1" +description = "Pytest support for asyncio." +category = "dev" +optional = false +python-versions = ">= 3.6" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +testing = ["coverage", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-forked" +version = "1.3.0" +description = "run tests in isolated forked subprocesses" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +py = "*" +pytest = ">=3.10" + +[[package]] +name = "pytest-xdist" +version = "2.2.1" +description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +execnet = ">=1.1" +psutil = {version = ">=3.0", optional = true, markers = "extra == \"psutil\""} +pytest = ">=6.0.0" +pytest-forked = "*" + +[package.extras] +psutil = ["psutil (>=3.0)"] +testing = ["filelock"] + +[[package]] +name = "python-cinderclient" +version = "5.0.2" +description = "OpenStack Block Storage API Client Library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=2.3.4,<2.4.0 || >2.4.0" +keystoneauth1 = ">=3.4.0" +"oslo.i18n" = ">=3.15.3" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PrettyTable = ">=0.7.1,<0.8" +requests = ">=2.14.2,<2.20.0 || >2.20.0" +simplejson = ">=3.5.1" +six = ">=1.10.0" + +[[package]] +name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "0.17.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-editor" +version = "1.0.4" +description = "Programmatically open an editor, capture the result." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "python-glanceclient" +version = "2.17.1" +description = "OpenStack Image API Client Library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +keystoneauth1 = ">=3.6.2" +"oslo.i18n" = ">=3.15.3" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PrettyTable = ">=0.7.1,<0.8" +pyOpenSSL = ">=17.1.0" +requests = ">=2.14.2" +six = ">=1.10.0" +warlock = ">=1.2.0,<2" +wrapt = ">=1.7.0" + +[[package]] +name = "python-heatclient" +version = "1.18.1" +description = "OpenStack Orchestration API Client Library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=2.3.4,<2.4.0 || >2.4.0" +cliff = ">=2.8.0,<2.9.0 || >2.9.0" +iso8601 = ">=0.1.11" +keystoneauth1 = ">=3.4.0" +osc-lib = ">=1.8.0" +"oslo.i18n" = ">=3.15.3" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PrettyTable = ">=0.7.2,<0.8" +python-swiftclient = ">=3.2.0" +PyYAML = ">=3.12" +requests = ">=2.14.2" +six = ">=1.10.0" + +[[package]] +name = "python-jose" +version = "3.2.0" +description = "JOSE implementation in Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +ecdsa = "<0.15" +pyasn1 = "*" +rsa = "*" +six = "<2.0" + +[package.extras] +cryptography = ["cryptography"] +pycrypto = ["pycrypto (>=2.6.0,<2.7.0)", "pyasn1"] +pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)", "pyasn1"] + +[[package]] +name = "python-keystoneclient" +version = "3.21.0" +description = "Client Library for OpenStack Identity" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +debtcollector = ">=1.2.0" +keystoneauth1 = ">=3.4.0" +"oslo.config" = ">=5.2.0" +"oslo.i18n" = ">=3.15.3" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +requests = ">=2.14.2" +six = ">=1.10.0" +stevedore = ">=1.20.0" + +[[package]] +name = "python-multipart" +version = "0.0.5" +description = "A streaming multipart parser for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.4.0" + +[[package]] +name = "python-neutronclient" +version = "6.14.1" +description = "CLI and Client Library for OpenStack Networking" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=2.3.4,<2.4.0 || >2.4.0" +cliff = ">=2.8.0,<2.9.0 || >2.9.0" +debtcollector = ">=1.2.0" +iso8601 = ">=0.1.11" +keystoneauth1 = ">=3.4.0" +netaddr = ">=0.7.18" +os-client-config = ">=1.28.0" +osc-lib = ">=1.8.0" +"oslo.i18n" = ">=3.15.3" +"oslo.log" = ">=3.36.0" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +python-keystoneclient = ">=3.8.0" +requests = ">=2.14.2" +simplejson = ">=3.5.1" +six = ">=1.10.0" + +[[package]] +name = "python-novaclient" +version = "15.1.1" +description = "Client library for OpenStack Compute API" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=2.3.4,<2.4.0 || >2.4.0" +iso8601 = ">=0.1.11" +keystoneauth1 = ">=3.5.0" +"oslo.i18n" = ">=3.15.3" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +PrettyTable = ">=0.7.2,<0.8" +simplejson = ">=3.5.1" +six = ">=1.10.0" + +[[package]] +name = "python-octaviaclient" +version = "1.10.1" +description = "Octavia client for OpenStack Load Balancing" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=2.3.4,<2.4.0 || >2.4.0" +cliff = ">=2.8.0,<2.9.0 || >2.9.0" +keystoneauth1 = ">=3.4.0" +netifaces = ">=0.10.4" +osc-lib = ">=1.8.0" +"oslo.serialization" = ">=2.18.0,<2.19.1 || >2.19.1" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +python-neutronclient = ">=6.7.0" +python-openstackclient = ">=3.12.0" +requests = ">=2.14.2" +six = ">=1.10.0" + +[[package]] +name = "python-openstackclient" +version = "4.0.2" +description = "OpenStack Command-line Client" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=2.3.4,<2.4.0 || >2.4.0" +cliff = ">=2.8.0,<2.9.0 || >2.9.0" +keystoneauth1 = ">=3.6.2" +openstacksdk = ">=0.17.0" +osc-lib = ">=1.14.0" +"oslo.i18n" = ">=3.15.3" +"oslo.utils" = ">=3.33.0" +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +python-cinderclient = ">=3.3.0" +python-glanceclient = ">=2.8.0" +python-keystoneclient = ">=3.17.0" +python-novaclient = ">=15.0.0" +six = ">=1.10.0" + +[[package]] +name = "python-swiftclient" +version = "3.11.1" +description = "OpenStack Object Storage API Client Library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=1.1.0" +six = ">=1.9.0" + +[package.extras] +keystone = ["python-keystoneclient (>=0.7.0)"] +test = ["coverage (>=4.0,!=4.4)", "keystoneauth1 (>=3.4.0)", "mock (>=1.2.0)", "openstacksdk (>=0.11.0)", "stestr (>=2.0.0,!=3.0.0)", "hacking (>=1.1.0,<1.2.0)", "hacking (>=3.2.0,<3.3.0)"] + +[[package]] +name = "pytz" +version = "2021.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "regex" +version = "2021.4.4" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.15.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "requestsexceptions" +version = "1.4.0" +description = "Import exceptions from potentially bundled packages in requests." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rsa" +version = "4.7.2" +description = "Pure-Python RSA implementation" +category = "main" +optional = false +python-versions = ">=3.5, <4" + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "rx" +version = "1.6.1" +description = "Reactive Extensions (Rx) for Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "simplejson" +version = "3.17.2" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +category = "main" +optional = false +python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "skyline-log" +version = "0.1.0" +description = "" +category = "main" +optional = false +python-versions = "^3.8" +develop = true + +[package.dependencies] +loguru = "*" + +[package.source] +type = "directory" +url = "libs/skyline-log" + +[[package]] +name = "skyline-policy-manager" +version = "0.1.0" +description = "" +category = "main" +optional = false +python-versions = "^3.8" +develop = true + +[package.dependencies] +click = "*" +"oslo.policy" = "*" +pydantic = "*" +skyline-log = "*" +Werkzeug = "*" + +[package.source] +type = "directory" +url = "libs/skyline-policy-manager" + +[[package]] +name = "smmap" +version = "4.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "sniffio" +version = "1.2.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "sqlalchemy" +version = "1.3.24" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mysql = ["mysqlclient"] +oracle = ["cx-oracle"] +postgresql = ["psycopg2"] +postgresql_pg8000 = ["pg8000 (<1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] + +[[package]] +name = "starlette" +version = "0.13.4" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] + +[[package]] +name = "stevedore" +version = "3.3.0" +description = "Manage dynamic plugins for Python applications" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "tokenize-rt" +version = "4.1.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "ujson" +version = "4.0.2" +description = "Ultra fast JSON encoder and decoder for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "uvicorn" +version = "0.12.3" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +click = ">=7.0.0,<8.0.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.1.0,<0.2.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchgod = {version = ">=0.6,<0.7", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=8.0.0,<9.0.0", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6,<0.7)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0)", "colorama (>=0.4)"] + +[[package]] +name = "uvloop" +version = "0.15.2" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)", "aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"] +test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] + +[[package]] +name = "virtualenv" +version = "20.4.6" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +name = "warlock" +version = "1.3.3" +description = "Python object model built on JSON schema and JSON patch." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +jsonpatch = ">=0.10,<2" +jsonschema = ">=0.7,<4" +six = "*" + +[[package]] +name = "watchgod" +version = "0.6" +description = "Simple, modern file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "websockets" +version = "8.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "werkzeug" +version = "2.0.1" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "win32-setctime" +version = "1.0.3" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] + +[[package]] +name = "wrapt" +version = "1.12.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "a648952047786ea05577906b590557f5da43a961d2f3725c23096310cbdeee1a" + +[metadata.files] +add-trailing-comma = [ + {file = "add_trailing_comma-2.1.0-py2.py3-none-any.whl", hash = "sha256:f462403aa2e997e20855708edb57536d1d3310d5c5fac7e80542578eb47fdb10"}, + {file = "add_trailing_comma-2.1.0.tar.gz", hash = "sha256:f9864ffbc12ea4e54916a356d57341ab58f612867c2ad453339c51004807e8ce"}, +] +aiofiles = [ + {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"}, + {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, +] +aiomysql = [ + {file = "aiomysql-0.0.21-py3-none-any.whl", hash = "sha256:a81a97da3dd732635926a8ea6adbbf2d1345799680bf61b5f89e730bcec88cc5"}, + {file = "aiomysql-0.0.21.tar.gz", hash = "sha256:811569c0db118dd2685f0878f5cebf17a11e89a995fa14261d5fa0254113842c"}, +] +aiosqlite = [ + {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, + {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, +] +alembic = [ + {file = "alembic-1.6.2.tar.gz", hash = "sha256:fb9a39a7c68e55490be962fb5f70463d384d340e6563d6e3911447778e3b4576"}, +] +aniso8601 = [ + {file = "aniso8601-7.0.0-py2.py3-none-any.whl", hash = "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b"}, + {file = "aniso8601-7.0.0.tar.gz", hash = "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e"}, +] +apipkg = [ + {file = "apipkg-1.5-py2.py3-none-any.whl", hash = "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c"}, + {file = "apipkg-1.5.tar.gz", hash = "sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6"}, +] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +asgi-lifespan = [ + {file = "asgi-lifespan-1.0.1.tar.gz", hash = "sha256:9a33e7da2073c4764bc79bd6136501d6c42f60e3d2168ba71235e84122eadb7f"}, + {file = "asgi_lifespan-1.0.1-py3-none-any.whl", hash = "sha256:9ea969dc5eb5cf08e52c08dce6f61afcadd28112e72d81c972b1d8eb8691ab53"}, +] +async-exit-stack = [ + {file = "async_exit_stack-1.0.1-py3-none-any.whl", hash = "sha256:9b43b17683b3438f428ef3bbec20689f5abbb052aa4b564c643397330adfaa99"}, + {file = "async_exit_stack-1.0.1.tar.gz", hash = "sha256:24de1ad6d0ff27be97c89d6709fa49bf20db179eaf1f4d2e6e9b4409b80e747d"}, +] +async-generator = [ + {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, + {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +babel = [ + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, +] +bandit = [ + {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"}, + {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"}, +] +bcrypt = [ + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, + {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, + {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] +cfgv = [ + {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, + {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +cliff = [ + {file = "cliff-3.7.0-py3-none-any.whl", hash = "sha256:9897aec5d621e652439a1c84bc7a9c3928397d9d0ac492fa960d3e0c3f0752f3"}, + {file = "cliff-3.7.0.tar.gz", hash = "sha256:389c81960de13f05daf1cbd546f33199e86c518ba4266c79ec7a153a280980ea"}, +] +cmd2 = [ + {file = "cmd2-1.5.0-py3-none-any.whl", hash = "sha256:31aaeb590ec7bd2cc2cee0172099cc6f0a1396f82ab7bc389dbff23d9b90f6ec"}, + {file = "cmd2-1.5.0.tar.gz", hash = "sha256:701a8c9975c4abc45e5d13906ab149f959f812869106347323a3f89ac0e82a62"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +cryptography = [ + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, +] +databases = [ + {file = "databases-0.4.3-py3-none-any.whl", hash = "sha256:f82b02c28fdddf7ffe7ee1945f5abef44d687ba97b9a1c81492c7f035d4c90e6"}, + {file = "databases-0.4.3.tar.gz", hash = "sha256:1521db7f6d3c581ff81b3552e130b27a13aefea2a57295e65738081831137afc"}, +] +debtcollector = [ + {file = "debtcollector-2.2.0-py3-none-any.whl", hash = "sha256:34663e5de257c67bf38827cfbea259c4d4ad27eba6b5a9d9242cb54076bfb4ad"}, + {file = "debtcollector-2.2.0.tar.gz", hash = "sha256:787981f4d235841bf6eb0467e23057fb1ac7ee24047c32028a8498b9128b6829"}, +] +decorator = [ + {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, + {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, +] +distlib = [ + {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, + {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, +] +dnspython = [ + {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, + {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, +] +"dogpile.cache" = [ + {file = "dogpile.cache-1.1.2-py3-none-any.whl", hash = "sha256:b13740e8100ed459928ac50d5ba9a4512934adecd6c0b9af93978789184076f4"}, + {file = "dogpile.cache-1.1.2.tar.gz", hash = "sha256:2134464672a3deb7ef1366a8691726686d8c62540e4208f1a40c9aaa1a0b6a45"}, +] +ecdsa = [ + {file = "ecdsa-0.14.1-py2.py3-none-any.whl", hash = "sha256:e108a5fe92c67639abae3260e43561af914e7fd0d27bae6d2ec1312ae7934dfe"}, + {file = "ecdsa-0.14.1.tar.gz", hash = "sha256:64c613005f13efec6541bb0a33290d0d03c27abab5f15fbab20fb0ee162bdd8e"}, +] +email-validator = [ + {file = "email-validator-1.1.2.tar.gz", hash = "sha256:1a13bd6050d1db4475f13e444e169b6fe872434922d38968c67cea9568cce2f0"}, + {file = "email_validator-1.1.2-py2.py3-none-any.whl", hash = "sha256:094b1d1c60d790649989d38d34f69e1ef07792366277a2cf88684d03495d018f"}, +] +execnet = [ + {file = "execnet-1.8.0-py2.py3-none-any.whl", hash = "sha256:7a13113028b1e1cc4c6492b28098b3c6576c9dccc7973bfe47b342afadafb2ac"}, + {file = "execnet-1.8.0.tar.gz", hash = "sha256:b73c5565e517f24b62dea8a5ceac178c661c4309d3aa0c3e420856c072c411b4"}, +] +fastapi = [ + {file = "fastapi-0.58.1-py3-none-any.whl", hash = "sha256:d7499761d5ca901cdf5b6b73018d14729593f8ab1ea22d241f82fa574fc406ad"}, + {file = "fastapi-0.58.1.tar.gz", hash = "sha256:92e59b77eef7d6eaa80b16d275adda06b5f33b12d777e3fc5521b2f7f4718e13"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] +gitdb = [ + {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, + {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, +] +gitpython = [ + {file = "GitPython-3.1.17-py3-none-any.whl", hash = "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135"}, + {file = "GitPython-3.1.17.tar.gz", hash = "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"}, +] +graphene = [ + {file = "graphene-2.1.8-py2.py3-none-any.whl", hash = "sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896"}, + {file = "graphene-2.1.8.tar.gz", hash = "sha256:2cbe6d4ef15cfc7b7805e0760a0e5b80747161ce1b0f990dfdc0d2cf497c12f9"}, +] +graphql-core = [ + {file = "graphql-core-2.3.2.tar.gz", hash = "sha256:aac46a9ac524c9855910c14c48fc5d60474def7f99fd10245e76608eba7af746"}, + {file = "graphql_core-2.3.2-py2.py3-none-any.whl", hash = "sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad"}, +] +graphql-relay = [ + {file = "graphql-relay-2.0.1.tar.gz", hash = "sha256:870b6b5304123a38a0b215a79eace021acce5a466bf40cd39fa18cb8528afabb"}, + {file = "graphql_relay-2.0.1-py3-none-any.whl", hash = "sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d"}, +] +gunicorn = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] +h11 = [ + {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, + {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, +] +hiyapyco = [ + {file = "HiYaPyCo-0.4.16-py2.py3-none-any.whl", hash = "sha256:568c99f5cd9ccbde4b76323fcedb8490d1b7c51a257d74580e262ceea7f8a65b"}, + {file = "HiYaPyCo-0.4.16.tar.gz", hash = "sha256:ffee19eea74ee4d92d02b67f4d08b3654182fb529affa608eaff7e09d27267e1"}, +] +httpcore = [ + {file = "httpcore-0.12.3-py3-none-any.whl", hash = "sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc"}, + {file = "httpcore-0.12.3.tar.gz", hash = "sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9"}, +] +httptools = [ + {file = "httptools-0.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:1e35aa179b67086cc600a984924a88589b90793c9c1b260152ca4908786e09df"}, + {file = "httptools-0.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c4111a0a8a00eff1e495d43ea5230aaf64968a48ddba8ea2d5f982efae827404"}, + {file = "httptools-0.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:dce59ee45dd6ee6c434346a5ac527c44014326f560866b4b2f414a692ee1aca8"}, + {file = "httptools-0.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f759717ca1b2ef498c67ba4169c2b33eecf943a89f5329abcff8b89d153eb500"}, + {file = "httptools-0.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:08b79e09114e6ab5c3dbf560bba2cb2257ea38cdaeaf99b7cb80d8f92622fcd9"}, + {file = "httptools-0.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8fcca4b7efe353b13a24017211334c57d055a6e132c7adffed13a10d28efca57"}, + {file = "httptools-0.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:aebdf0bd7bf7c90ae6b3be458692bf6e9e5b610b501f9f74c7979015a51db4c4"}, + {file = "httptools-0.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:fbf7ecd31c39728f251b1c095fd27c84e4d21f60a1d079a0333472ff3ae59d34"}, + {file = "httptools-0.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1c63d860749841024951b0a78e4dec6f543d23751ef061d6ab60064c7b8b524"}, + {file = "httptools-0.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fb7199b8fb0c50a22e77260bb59017e0c075fa80cb03bb2c8692de76e7bb7fe7"}, + {file = "httptools-0.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bda99a5723e7eab355ce57435c70853fc137a65aebf2f1cd4d15d96e2956da7b"}, + {file = "httptools-0.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:851026bd63ec0af7e7592890d97d15c92b62d9e17094353f19a52c8e2b33710a"}, + {file = "httptools-0.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:31629e1f1b89959f8c0927bad12184dc07977dcf71e24f4772934aa490aa199b"}, + {file = "httptools-0.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:9abd788465aa46a0f288bd3a99e53edd184177d6379e2098fd6097bb359ad9d6"}, + {file = "httptools-0.1.2.tar.gz", hash = "sha256:07659649fe6b3948b6490825f89abe5eb1cec79ebfaaa0b4bf30f3f33f3c2ba8"}, +] +httpx = [ + {file = "httpx-0.16.1-py3-none-any.whl", hash = "sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b"}, + {file = "httpx-0.16.1.tar.gz", hash = "sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537"}, +] +identify = [ + {file = "identify-2.2.4-py2.py3-none-any.whl", hash = "sha256:ad9f3fa0c2316618dc4d840f627d474ab6de106392a4f00221820200f490f5a8"}, + {file = "identify-2.2.4.tar.gz", hash = "sha256:9bcc312d4e2fa96c7abebcdfb1119563b511b5e3985ac52f60d9116277865b2e"}, +] +idna = [ + {file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"}, + {file = "idna-3.1.tar.gz", hash = "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"}, +] +immutables = [ + {file = "immutables-0.15-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6728f4392e3e8e64b593a5a0cd910a1278f07f879795517e09f308daed138631"}, + {file = "immutables-0.15-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f0836cd3bdc37c8a77b192bbe5f41dbcc3ce654db048ebbba89bdfe6db7a1c7a"}, + {file = "immutables-0.15-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:8703d8abfd8687932f2a05f38e7de270c3a6ca3bd1c1efb3c938656b3f2f985a"}, + {file = "immutables-0.15-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b8ad986f9b532c026f19585289384b0769188fcb68b37c7f0bd0df9092a6ca54"}, + {file = "immutables-0.15-cp36-cp36m-win_amd64.whl", hash = "sha256:6f117d9206165b9dab8fd81c5129db757d1a044953f438654236ed9a7a4224ae"}, + {file = "immutables-0.15-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b75ade826920c4e490b1bb14cf967ac14e61eb7c5562161c5d7337d61962c226"}, + {file = "immutables-0.15-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b7e13c061785e34f73c4f659861f1b3e4a5fd918e4395c84b21c4e3d449ebe27"}, + {file = "immutables-0.15-cp37-cp37m-win_amd64.whl", hash = "sha256:3035849accee4f4e510ed7c94366a40e0f5fef9069fbe04a35f4787b13610a4a"}, + {file = "immutables-0.15-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b04fa69174e0c8f815f9c55f2a43fc9e5a68452fab459a08e904a74e8471639f"}, + {file = "immutables-0.15-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:141c2e9ea515a3a815007a429f0b47a578ebeb42c831edaec882a245a35fffca"}, + {file = "immutables-0.15-cp38-cp38-win_amd64.whl", hash = "sha256:cbe8c64640637faa5535d539421b293327f119c31507c33ca880bd4f16035eb6"}, + {file = "immutables-0.15-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:a0a4e4417d5ef4812d7f99470cd39347b58cb927365dd2b8da9161040d260db0"}, + {file = "immutables-0.15-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3b15c08c71c59e5b7c2470ef949d49ff9f4263bb77f488422eaa157da84d6999"}, + {file = "immutables-0.15-cp39-cp39-win_amd64.whl", hash = "sha256:2283a93c151566e6830aee0e5bee55fc273455503b43aa004356b50f9182092b"}, + {file = "immutables-0.15.tar.gz", hash = "sha256:3713ab1ebbb6946b7ce1387bb9d1d7f5e09c45add58c2a2ee65f963c171e746b"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +iso8601 = [ + {file = "iso8601-0.1.14-py2.py3-none-any.whl", hash = "sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004"}, + {file = "iso8601-0.1.14.tar.gz", hash = "sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79"}, +] +isort = [ + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, +] +itsdangerous = [ + {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, + {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, +] +jinja2 = [ + {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, + {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, +] +jmespath = [ + {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, + {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, +] +jsonpatch = [ + {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"}, + {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"}, +] +jsonpointer = [ + {file = "jsonpointer-2.1-py2.py3-none-any.whl", hash = "sha256:150f80c5badd02c757da6644852f612f88e8b4bc2f9852dcbf557c8738919686"}, + {file = "jsonpointer-2.1.tar.gz", hash = "sha256:5a34b698db1eb79ceac454159d3f7c12a451a91f6334a4f638454327b7a89962"}, +] +jsonschema = [ + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, +] +keystoneauth1 = [ + {file = "keystoneauth1-3.17.3-py2.py3-none-any.whl", hash = "sha256:027674dbe8dc483e19109eed2b6f6dac5a0e687c91dde6d7e704180300fed12d"}, + {file = "keystoneauth1-3.17.3.tar.gz", hash = "sha256:bc5eef389daba1a0b881ea5c83ccb47604370a25aacba34fdc967cdf224c621b"}, +] +loguru = [ + {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, + {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, +] +mako = [ + {file = "Mako-1.1.4-py2.py3-none-any.whl", hash = "sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e"}, + {file = "Mako-1.1.4.tar.gz", hash = "sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab"}, +] +markupsafe = [ + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +msgpack = [ + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b6d9e2dae081aa35c44af9c4298de4ee72991305503442a5c74656d82b581fe9"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a99b144475230982aee16b3d249170f1cccebf27fb0a08e9f603b69637a62192"}, + {file = "msgpack-1.0.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1026dcc10537d27dd2d26c327e552f05ce148977e9d7b9f1718748281b38c841"}, + {file = "msgpack-1.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fe07bc6735d08e492a327f496b7850e98cb4d112c56df69b0c844dbebcbb47f6"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9ea52fff0473f9f3000987f313310208c879493491ef3ccf66268eff8d5a0326"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26a1759f1a88df5f1d0b393eb582ec022326994e311ba9c5818adc5374736439"}, + {file = "msgpack-1.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:497d2c12426adcd27ab83144057a705efb6acc7e85957a51d43cdcf7f258900f"}, + {file = "msgpack-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:e89ec55871ed5473a041c0495b7b4e6099f6263438e0bd04ccd8418f92d5d7f2"}, + {file = "msgpack-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4355d2193106c7aa77c98fc955252a737d8550320ecdb2e9ac701e15e2943bc"}, + {file = "msgpack-1.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:d6c64601af8f3893d17ec233237030e3110f11b8a962cb66720bf70c0141aa54"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f484cd2dca68502de3704f056fa9b318c94b1539ed17a4c784266df5d6978c87"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f3e6aaf217ac1c7ce1563cf52a2f4f5d5b1f64e8729d794165db71da57257f0c"}, + {file = "msgpack-1.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8521e5be9e3b93d4d5e07cb80b7e32353264d143c1f072309e1863174c6aadb1"}, + {file = "msgpack-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:31c17bbf2ae5e29e48d794c693b7ca7a0c73bd4280976d408c53df421e838d2a"}, + {file = "msgpack-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8ffb24a3b7518e843cd83538cf859e026d24ec41ac5721c18ed0c55101f9775b"}, + {file = "msgpack-1.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b28c0876cce1466d7c2195d7658cf50e4730667196e2f1355c4209444717ee06"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87869ba567fe371c4555d2e11e4948778ab6b59d6cc9d8460d543e4cfbbddd1c"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b55f7db883530b74c857e50e149126b91bb75d35c08b28db12dcb0346f15e46e"}, + {file = "msgpack-1.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ac25f3e0513f6673e8b405c3a80500eb7be1cf8f57584be524c4fa78fe8e0c83"}, + {file = "msgpack-1.0.2-cp38-cp38-win32.whl", hash = "sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9"}, + {file = "msgpack-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:e36a812ef4705a291cdb4a2fd352f013134f26c6ff63477f20235138d1d21009"}, + {file = "msgpack-1.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2a5866bdc88d77f6e1370f82f2371c9bc6fc92fe898fa2dec0c5d4f5435a2694"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:92be4b12de4806d3c36810b0fe2aeedd8d493db39e2eb90742b9c09299eb5759"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6bd7990a2c2dabe926b7e62a92886ccbf809425c347ae7de277067f97c2887"}, + {file = "msgpack-1.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5a9ee2540c78659a1dd0b110f73773533ee3108d4e1219b5a15a8d635b7aca0e"}, + {file = "msgpack-1.0.2-cp39-cp39-win32.whl", hash = "sha256:c747c0cc08bd6d72a586310bda6ea72eeb28e7505990f342552315b229a19b33"}, + {file = "msgpack-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:d8167b84af26654c1124857d71650404336f4eb5cc06900667a493fc619ddd9f"}, + {file = "msgpack-1.0.2.tar.gz", hash = "sha256:fae04496f5bc150eefad4e9571d1a76c55d021325dcd484ce45065ebbdd00984"}, +] +munch = [ + {file = "munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd"}, + {file = "munch-2.5.0.tar.gz", hash = "sha256:2d735f6f24d4dba3417fa448cae40c6e896ec1fdab6cdb5e6510999758a4dbd2"}, +] +mypy = [ + {file = "mypy-0.812-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49"}, + {file = "mypy-0.812-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c"}, + {file = "mypy-0.812-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521"}, + {file = "mypy-0.812-cp35-cp35m-win_amd64.whl", hash = "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"}, + {file = "mypy-0.812-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a"}, + {file = "mypy-0.812-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c"}, + {file = "mypy-0.812-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6"}, + {file = "mypy-0.812-cp36-cp36m-win_amd64.whl", hash = "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064"}, + {file = "mypy-0.812-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56"}, + {file = "mypy-0.812-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8"}, + {file = "mypy-0.812-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7"}, + {file = "mypy-0.812-cp37-cp37m-win_amd64.whl", hash = "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564"}, + {file = "mypy-0.812-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506"}, + {file = "mypy-0.812-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5"}, + {file = "mypy-0.812-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66"}, + {file = "mypy-0.812-cp38-cp38-win_amd64.whl", hash = "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e"}, + {file = "mypy-0.812-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a"}, + {file = "mypy-0.812-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a"}, + {file = "mypy-0.812-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97"}, + {file = "mypy-0.812-cp39-cp39-win_amd64.whl", hash = "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df"}, + {file = "mypy-0.812-py3-none-any.whl", hash = "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4"}, + {file = "mypy-0.812.tar.gz", hash = "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +netaddr = [ + {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, + {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, +] +netifaces = [ + {file = "netifaces-0.10.9-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29"}, + {file = "netifaces-0.10.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b"}, + {file = "netifaces-0.10.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc"}, + {file = "netifaces-0.10.9-cp27-cp27m-win32.whl", hash = "sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2"}, + {file = "netifaces-0.10.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b"}, + {file = "netifaces-0.10.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084"}, + {file = "netifaces-0.10.9-cp33-cp33m-manylinux1_i686.whl", hash = "sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994"}, + {file = "netifaces-0.10.9-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940"}, + {file = "netifaces-0.10.9-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7"}, + {file = "netifaces-0.10.9-cp34-cp34m-win32.whl", hash = "sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24"}, + {file = "netifaces-0.10.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea"}, + {file = "netifaces-0.10.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b"}, + {file = "netifaces-0.10.9-cp35-cp35m-win32.whl", hash = "sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae"}, + {file = "netifaces-0.10.9-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc"}, + {file = "netifaces-0.10.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89"}, + {file = "netifaces-0.10.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c"}, + {file = "netifaces-0.10.9-cp36-cp36m-win32.whl", hash = "sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe"}, + {file = "netifaces-0.10.9-cp36-cp36m-win_amd64.whl", hash = "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215"}, + {file = "netifaces-0.10.9-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa"}, + {file = "netifaces-0.10.9-cp37-cp37m-win32.whl", hash = "sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1"}, + {file = "netifaces-0.10.9-cp37-cp37m-win_amd64.whl", hash = "sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42"}, + {file = "netifaces-0.10.9.tar.gz", hash = "sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3"}, +] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +] +openstacksdk = [ + {file = "openstacksdk-0.36.5-py2.py3-none-any.whl", hash = "sha256:3e06b6b2e07e195e5b9a905a9c3c0b067e361340f0b225c96d019117a5598622"}, + {file = "openstacksdk-0.36.5.tar.gz", hash = "sha256:9c2c35552c33d205191ed244cc1e5edf633396aa16cde6ced65654ff065ed167"}, +] +orjson = [ + {file = "orjson-3.5.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:2ba4165883fbef0985bce60bddbf91bc5cea77cc22b1c12fe7a716c6323ab1e7"}, + {file = "orjson-3.5.2-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:cee746d186ba9efa47b9d52a649ee0617456a9a4d7a2cbd3ec06330bb9cb372a"}, + {file = "orjson-3.5.2-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:8591a25a31a89cf2a33e30eb516ab028bad2c72fed04e323917114aaedc07c7d"}, + {file = "orjson-3.5.2-cp36-cp36m-macosx_10_9_universal2.whl", hash = "sha256:38cb8cdbf43eafc6dcbfb10a9e63c80727bb916aee0f75caf5f90e5355b266e1"}, + {file = "orjson-3.5.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:96b403796fc7e44bae843a2a83923925fe048f3a67c10a298fdfc0ff46163c14"}, + {file = "orjson-3.5.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:5b66a62d4c0c44441b23fafcd3d0892296d9793361b14bcc5a5645c88b6a4a71"}, + {file = "orjson-3.5.2-cp36-none-win_amd64.whl", hash = "sha256:609e93919268fadb871aafb7f550c3fe8d3e8c1305cadcc1610b414113b7034e"}, + {file = "orjson-3.5.2-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:200bd4491052d13696456a92d23f086b68b526c2464248733964e8165ac60888"}, + {file = "orjson-3.5.2-cp37-cp37m-macosx_10_9_universal2.whl", hash = "sha256:cc614bf6bfe0181e51dd98a9c53669f08d4d8641efbf1a287113da3059773dea"}, + {file = "orjson-3.5.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:43576bed3be300e9c02629a8d5fb3340fe6474765e6eee9610067def4b3ac19c"}, + {file = "orjson-3.5.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:acd735718b531b78858a7e932c58424c5a3e39e04d61bba3d95ce8a8498ea9e9"}, + {file = "orjson-3.5.2-cp37-none-win_amd64.whl", hash = "sha256:7503145ffd1ae90d487860b97e2867ec61c2c8f001209bb12700ba7833df8ddf"}, + {file = "orjson-3.5.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:9c37cf3dbc9c81abed04ba4854454e9f0d8ac7c05fb6c4f36545733e90be6af2"}, + {file = "orjson-3.5.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e6ef00ddc637b7d13926aaccdabac363efdfd348c132410eb054c27e2eae6a7"}, + {file = "orjson-3.5.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:9d0834ca40c6e467fa1f1db3f83a8c3562c03eb2b7067ad09de5019592edb88f"}, + {file = "orjson-3.5.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:d4a2ddc6342a8280dafaa69827b387b95856ef0a6c5812fe91f5bd21ddd2ef36"}, + {file = "orjson-3.5.2-cp38-none-win_amd64.whl", hash = "sha256:f54f8bcf24812a524e8904a80a365f7a287d82fc6ebdee528149616070abe5ab"}, + {file = "orjson-3.5.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8b429471398ea37d848fb53bca6a8c42fb776c278f4fcb6a1d651b8f1fb64947"}, + {file = "orjson-3.5.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13fd458110fbe019c2a67ee539678189444f73bc09b27983c9b42663c63e0445"}, + {file = "orjson-3.5.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8bf1145a06e1245f0c8a8c32df6ffe52d214eb4eb88c3fb32e4ed14e3dc38e0e"}, + {file = "orjson-3.5.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:7e3434010e3f0680e92bb0a6094e4d5c939d0c4258c76397c6bd5263c7d62e86"}, + {file = "orjson-3.5.2-cp39-none-win_amd64.whl", hash = "sha256:df9730cc8cd22b3f54aa55317257f3279e6300157fc0f4ed4424586cd7eb012d"}, + {file = "orjson-3.5.2.tar.gz", hash = "sha256:f385253a6ddac37ea422ec2c0d35772b4f5bf0dc0803ce44543bf7e530423ef8"}, +] +os-client-config = [ + {file = "os-client-config-2.1.0.tar.gz", hash = "sha256:abc38a351f8c006d34f7ee5f3f648de5e3ecf6455cc5d76cfd889d291cdf3f4e"}, + {file = "os_client_config-2.1.0-py3-none-any.whl", hash = "sha256:27bdc338f8a872814dc77ff3427844b4ea483cf95796571273aaf50dd1faf770"}, +] +os-service-types = [ + {file = "os-service-types-1.7.0.tar.gz", hash = "sha256:31800299a82239363995b91f1ebf9106ac7758542a1e4ef6dc737a5932878c6c"}, + {file = "os_service_types-1.7.0-py2.py3-none-any.whl", hash = "sha256:0505c72205690910077fb72b88f2a1f07533c8d39f2fe75b29583481764965d6"}, +] +osc-lib = [ + {file = "osc-lib-2.4.0.tar.gz", hash = "sha256:47029225c19a053a063736da390446debb5f14224dc60e92df7ca1b694625a9f"}, + {file = "osc_lib-2.4.0-py3-none-any.whl", hash = "sha256:94faf037494ca5ebe17ec50ea06d97890bd47f7d7f30553c657cb66b0e6fd1ce"}, +] +osc-placement = [ + {file = "osc-placement-1.7.0.tar.gz", hash = "sha256:68ac7dc7c58982a3b0cf625108e1d74092c01ec38a25769270074bc8093dcac1"}, + {file = "osc_placement-1.7.0-py2.py3-none-any.whl", hash = "sha256:fd6b867af2f0cd37ef8fe1e96c75d98c102ab14f0dbd5ddd5b32f6a1551246b0"}, +] +"oslo.config" = [ + {file = "oslo.config-6.3.0-py2.py3-none-any.whl", hash = "sha256:a7185f2ffedfd60e7996b9ac2bd0f2b23a5df613f618e7dcb80e7562497d81ec"}, + {file = "oslo.config-6.3.0.tar.gz", hash = "sha256:bffe681bed69882ca8fa12df07b8d5194bbebb54f6ccf73ad096d5f5535ef450"}, +] +"oslo.context" = [ + {file = "oslo.context-3.2.0-py3-none-any.whl", hash = "sha256:ed71f2431bfc71b2cdb220ae2206a3dd22c621b5cbc9241bb69998a47a591d8c"}, + {file = "oslo.context-3.2.0.tar.gz", hash = "sha256:0e7d96a95c276de2da0f458ef34153347327ac34fe33b264be2bb59eac51e620"}, +] +"oslo.i18n" = [ + {file = "oslo.i18n-5.0.1-py3-none-any.whl", hash = "sha256:99a6453b9b7a9d1603ba6c32e6ab8c738af95f6573215682a33c8028340bdccd"}, + {file = "oslo.i18n-5.0.1.tar.gz", hash = "sha256:3484b71e30f75c437523302d1151c291caf4098928269ceec65ce535456e035b"}, +] +"oslo.log" = [ + {file = "oslo.log-4.5.0-py3-none-any.whl", hash = "sha256:8539cfa12f49c52d3ccfaf29c72935c80d784bce90a8733684db2b830be6a33c"}, + {file = "oslo.log-4.5.0.tar.gz", hash = "sha256:63619fc00483f37c7ae6a6d6775d1ebb2b57e241c731f0295d1473b2c01c7278"}, +] +"oslo.policy" = [ + {file = "oslo.policy-3.8.0-py3-none-any.whl", hash = "sha256:15a6e3e6677ada933d48b05b29e0c73a4d63b7267f0ec183c05ba7d7e01310c6"}, + {file = "oslo.policy-3.8.0.tar.gz", hash = "sha256:31f4a4c94fddc1247e829349575b4358e21412ee0a4d4d9c1b37ba6be8c3f022"}, +] +"oslo.serialization" = [ + {file = "oslo.serialization-4.1.0-py3-none-any.whl", hash = "sha256:a0acf0ff7ca88b3ee6514713571f614b5c20870005ed0eb90408fa7f9f3edb60"}, + {file = "oslo.serialization-4.1.0.tar.gz", hash = "sha256:cecc7794df806c85cb70dbd6c2b3af19bc68047ad29e3c6442be90a0a4de5379"}, +] +"oslo.utils" = [ + {file = "oslo.utils-4.9.0-py3-none-any.whl", hash = "sha256:c10740e4462c32956afa1ba5156191cb678b2b63c60485d1a73542d1622677d6"}, + {file = "oslo.utils-4.9.0.tar.gz", hash = "sha256:4d29cae87f0ce48ec0e1c5577787c5cd10723c614f2d5a642f42c5efdf787e9c"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +passlib = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pbr = [ + {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"}, + {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +pre-commit = [ + {file = "pre_commit-2.12.1-py2.py3-none-any.whl", hash = "sha256:70c5ec1f30406250b706eda35e868b87e3e4ba099af8787e3e8b4b01e84f4712"}, + {file = "pre_commit-2.12.1.tar.gz", hash = "sha256:900d3c7e1bf4cf0374bb2893c24c23304952181405b4d88c9c40b72bda1bb8a9"}, +] +prettytable = [ + {file = "prettytable-0.7.2.tar.bz2", hash = "sha256:853c116513625c738dc3ce1aee148b5b5757a86727e67eff6502c7ca59d43c36"}, + {file = "prettytable-0.7.2.tar.gz", hash = "sha256:2d5460dc9db74a32bcc8f9f67de68b2c4f4d2f01fa3bd518764c69156d9cacd9"}, + {file = "prettytable-0.7.2.zip", hash = "sha256:a53da3b43d7a5c229b5e3ca2892ef982c46b7923b51e98f0db49956531211c4f"}, +] +promise = [ + {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"}, +] +psutil = [ + {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, + {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"}, + {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"}, + {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"}, + {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"}, + {file = "psutil-5.8.0-cp27-none-win32.whl", hash = "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"}, + {file = "psutil-5.8.0-cp27-none-win_amd64.whl", hash = "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"}, + {file = "psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"}, + {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"}, + {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"}, + {file = "psutil-5.8.0-cp36-cp36m-win32.whl", hash = "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"}, + {file = "psutil-5.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"}, + {file = "psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"}, + {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"}, + {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"}, + {file = "psutil-5.8.0-cp37-cp37m-win32.whl", hash = "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"}, + {file = "psutil-5.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"}, + {file = "psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"}, + {file = "psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"}, + {file = "psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"}, + {file = "psutil-5.8.0-cp38-cp38-win32.whl", hash = "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"}, + {file = "psutil-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"}, + {file = "psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"}, + {file = "psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"}, + {file = "psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"}, + {file = "psutil-5.8.0-cp39-cp39-win32.whl", hash = "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"}, + {file = "psutil-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"}, + {file = "psutil-5.8.0.tar.gz", hash = "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pyasn1 = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] +pycodestyle = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] +pyinotify = [ + {file = "pyinotify-0.9.6.tar.gz", hash = "sha256:9c998a5d7606ca835065cdabc013ae6c66eb9ea76a00a1e3bc6e0cfe2b4f71f4"}, +] +pympler = [ + {file = "Pympler-0.9.tar.gz", hash = "sha256:f2cbe7df622117af890249f2dea884eb702108a12d729d264b7c5983a6e06e47"}, +] +pymysql = [ + {file = "PyMySQL-0.9.3-py2.py3-none-any.whl", hash = "sha256:3943fbbbc1e902f41daf7f9165519f140c4451c179380677e6a848587042561a"}, + {file = "PyMySQL-0.9.3.tar.gz", hash = "sha256:d8c059dcd81dedb85a9f034d5e22dcb4442c0b201908bede99e306d65ea7c8e7"}, +] +pyopenssl = [ + {file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"}, + {file = "pyOpenSSL-20.0.1.tar.gz", hash = "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pyperclip = [ + {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, +] +pyperf = [ + {file = "pyperf-2.2.0-py2.py3-none-any.whl", hash = "sha256:d148650ebb99f7073b5b9cbe77c86094536095a19e5c546b268ad3b64708dc86"}, + {file = "pyperf-2.2.0.tar.gz", hash = "sha256:498bb4d1fe21350c2b7c1aa8bb3eae9c9979358d0b66327954bc66839fcba8b6"}, +] +pyreadline3 = [ + {file = "pyreadline3-3.3-py3-none-any.whl", hash = "sha256:0003fd0079d152ecbd8111202c5a7dfa6a5569ffd65b235e45f3c2ecbee337b4"}, + {file = "pyreadline3-3.3.tar.gz", hash = "sha256:ff3b5a1ac0010d0967869f723e687d42cabc7dccf33b14934c92aa5168d260b3"}, +] +pyrsistent = [ + {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, +] +pytest = [ + {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, + {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, + {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}, +] +pytest-forked = [ + {file = "pytest-forked-1.3.0.tar.gz", hash = "sha256:6aa9ac7e00ad1a539c41bec6d21011332de671e938c7637378ec9710204e37ca"}, + {file = "pytest_forked-1.3.0-py2.py3-none-any.whl", hash = "sha256:dc4147784048e70ef5d437951728825a131b81714b398d5d52f17c7c144d8815"}, +] +pytest-xdist = [ + {file = "pytest-xdist-2.2.1.tar.gz", hash = "sha256:718887296892f92683f6a51f25a3ae584993b06f7076ce1e1fd482e59a8220a2"}, + {file = "pytest_xdist-2.2.1-py3-none-any.whl", hash = "sha256:2447a1592ab41745955fb870ac7023026f20a5f0bfccf1b52a879bd193d46450"}, +] +python-cinderclient = [ + {file = "python-cinderclient-5.0.2.tar.gz", hash = "sha256:4fa0822cdf9eae8ee2c4a5c6625a17ad36205ace8fee2fa02d2229f86a0aeb94"}, + {file = "python_cinderclient-5.0.2-py2.py3-none-any.whl", hash = "sha256:895e79be709d476d168e6966491aeb2587606f16af67592eedfa59c3be25d9c3"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +python-dotenv = [ + {file = "python-dotenv-0.17.1.tar.gz", hash = "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f"}, + {file = "python_dotenv-0.17.1-py2.py3-none-any.whl", hash = "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544"}, +] +python-editor = [ + {file = "python-editor-1.0.4.tar.gz", hash = "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b"}, + {file = "python_editor-1.0.4-py2-none-any.whl", hash = "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"}, + {file = "python_editor-1.0.4-py2.7.egg", hash = "sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"}, + {file = "python_editor-1.0.4-py3-none-any.whl", hash = "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d"}, + {file = "python_editor-1.0.4-py3.5.egg", hash = "sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77"}, +] +python-glanceclient = [ + {file = "python-glanceclient-2.17.1.tar.gz", hash = "sha256:342ae26f9be89fef5929384843b8f0601dd1149a618329774b846cb3540fac8e"}, + {file = "python_glanceclient-2.17.1-py2.py3-none-any.whl", hash = "sha256:e537fc6e17b5bae6ed9fa296544882d0131b92e6a676f03d14309851839096f0"}, +] +python-heatclient = [ + {file = "python-heatclient-1.18.1.tar.gz", hash = "sha256:299b6048fb87e53af358a4a71ef6c1f39237187b402bb132e9bb5802d96ac35a"}, + {file = "python_heatclient-1.18.1-py2.py3-none-any.whl", hash = "sha256:c08a22020fc348c40f9b73cb518d40f0852008c97467c956c3c28a46f918d7aa"}, +] +python-jose = [ + {file = "python-jose-3.2.0.tar.gz", hash = "sha256:4e4192402e100b5fb09de5a8ea6bcc39c36ad4526341c123d401e2561720335b"}, + {file = "python_jose-3.2.0-py2.py3-none-any.whl", hash = "sha256:67d7dfff599df676b04a996520d9be90d6cdb7e6dd10b4c7cacc0c3e2e92f2be"}, +] +python-keystoneclient = [ + {file = "python-keystoneclient-3.21.0.tar.gz", hash = "sha256:1a34aa80bc65bbe9b3beb24df317e773d98a270cdd53a307fd5c00cfa833ea0e"}, + {file = "python_keystoneclient-3.21.0-py2.py3-none-any.whl", hash = "sha256:0825eed18fb854b0bd106b45adff227ee01d0315462b1e66c63278e878e35c87"}, +] +python-multipart = [ + {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, +] +python-neutronclient = [ + {file = "python-neutronclient-6.14.1.tar.gz", hash = "sha256:c53c5b9a1df8b2bc0fef949c40752a5030cdb2f6aa435464c454e2dca5f0f531"}, + {file = "python_neutronclient-6.14.1-py2.py3-none-any.whl", hash = "sha256:af9a81a9ea6d673a0b8d87a6114b835d13abddaad8c43ec5b050fe1e9f350e91"}, +] +python-novaclient = [ + {file = "python-novaclient-15.1.1.tar.gz", hash = "sha256:2d7e3d26ca5e14cdef79a8c6cbf4890d3fb1a83d64f1dedc6f15b56eb9077289"}, + {file = "python_novaclient-15.1.1-py2.py3-none-any.whl", hash = "sha256:2e7bb7a033b068e756e0ee70454887c6e17fa588bd17eef8ec5fd13ad9199966"}, +] +python-octaviaclient = [ + {file = "python-octaviaclient-1.10.1.tar.gz", hash = "sha256:5d887a58105a77c566fd835af30de11af07d8a5b13a4bdcaff0d940f371c6f25"}, + {file = "python_octaviaclient-1.10.1-py3-none-any.whl", hash = "sha256:983e8d93a482cc50bf844536f9e7043f153bb8d3c720b28e238d54aced09c273"}, +] +python-openstackclient = [ + {file = "python-openstackclient-4.0.2.tar.gz", hash = "sha256:b82edfe707988fe3b0eefc125f8775394289dc50e604f6a2aa814e535e4a764a"}, + {file = "python_openstackclient-4.0.2-py2.py3-none-any.whl", hash = "sha256:06f728711b3f19582637b7227656d0a4d330d2a67cabd586be56f9f4713a97c0"}, +] +python-swiftclient = [ + {file = "python-swiftclient-3.11.1.tar.gz", hash = "sha256:06919d59676d3e215f4da4f3f930d71880dda3528289842b25199509df712411"}, + {file = "python_swiftclient-3.11.1-py2.py3-none-any.whl", hash = "sha256:eb53bf614eb276896002884c9cf5c2bbdff56da49e9e343df088d180baf8c685"}, +] +pytz = [ + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +regex = [ + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, +] +requests = [ + {file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"}, + {file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"}, +] +requestsexceptions = [ + {file = "requestsexceptions-1.4.0-py2.py3-none-any.whl", hash = "sha256:3083d872b6e07dc5c323563ef37671d992214ad9a32b0ca4a3d7f5500bf38ce3"}, + {file = "requestsexceptions-1.4.0.tar.gz", hash = "sha256:b095cbc77618f066d459a02b137b020c37da9f46d9b057704019c9f77dba3065"}, +] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] +rsa = [ + {file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"}, + {file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"}, +] +rx = [ + {file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"}, + {file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"}, +] +simplejson = [ + {file = "simplejson-3.17.2-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8"}, + {file = "simplejson-3.17.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6"}, + {file = "simplejson-3.17.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d"}, + {file = "simplejson-3.17.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043"}, + {file = "simplejson-3.17.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4"}, + {file = "simplejson-3.17.2-cp27-cp27m-win32.whl", hash = "sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9"}, + {file = "simplejson-3.17.2-cp27-cp27m-win_amd64.whl", hash = "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667"}, + {file = "simplejson-3.17.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06"}, + {file = "simplejson-3.17.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f"}, + {file = "simplejson-3.17.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b"}, + {file = "simplejson-3.17.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f"}, + {file = "simplejson-3.17.2-cp33-cp33m-win32.whl", hash = "sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746"}, + {file = "simplejson-3.17.2-cp33-cp33m-win_amd64.whl", hash = "sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748"}, + {file = "simplejson-3.17.2-cp34-cp34m-win32.whl", hash = "sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3"}, + {file = "simplejson-3.17.2-cp34-cp34m-win_amd64.whl", hash = "sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b"}, + {file = "simplejson-3.17.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d"}, + {file = "simplejson-3.17.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956"}, + {file = "simplejson-3.17.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d"}, + {file = "simplejson-3.17.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971"}, + {file = "simplejson-3.17.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a"}, + {file = "simplejson-3.17.2-cp35-cp35m-win32.whl", hash = "sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396"}, + {file = "simplejson-3.17.2-cp35-cp35m-win_amd64.whl", hash = "sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a"}, + {file = "simplejson-3.17.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb"}, + {file = "simplejson-3.17.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb"}, + {file = "simplejson-3.17.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f"}, + {file = "simplejson-3.17.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45"}, + {file = "simplejson-3.17.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139"}, + {file = "simplejson-3.17.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46"}, + {file = "simplejson-3.17.2-cp36-cp36m-win32.whl", hash = "sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b"}, + {file = "simplejson-3.17.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625"}, + {file = "simplejson-3.17.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25"}, + {file = "simplejson-3.17.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf"}, + {file = "simplejson-3.17.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0"}, + {file = "simplejson-3.17.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f"}, + {file = "simplejson-3.17.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04"}, + {file = "simplejson-3.17.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278"}, + {file = "simplejson-3.17.2-cp37-cp37m-win32.whl", hash = "sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34"}, + {file = "simplejson-3.17.2-cp37-cp37m-win_amd64.whl", hash = "sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0"}, + {file = "simplejson-3.17.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da"}, + {file = "simplejson-3.17.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a"}, + {file = "simplejson-3.17.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995"}, + {file = "simplejson-3.17.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc"}, + {file = "simplejson-3.17.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94"}, + {file = "simplejson-3.17.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8"}, + {file = "simplejson-3.17.2.tar.gz", hash = "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +skyline-log = [] +skyline-policy-manager = [] +smmap = [ + {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, + {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, +] +sniffio = [ + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, + {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, +] +starlette = [ + {file = "starlette-0.13.4-py3-none-any.whl", hash = "sha256:0fb4b38d22945b46acb880fedee7ee143fd6c0542992501be8c45c0ed737dd1a"}, + {file = "starlette-0.13.4.tar.gz", hash = "sha256:04fe51d86fd9a594d9b71356ed322ccde5c9b448fc716ac74155e5821a922f8d"}, +] +stevedore = [ + {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, + {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, +] +tokenize-rt = [ + {file = "tokenize_rt-4.1.0-py2.py3-none-any.whl", hash = "sha256:b37251fa28c21e8cce2e42f7769a35fba2dd2ecafb297208f9a9a8add3ca7793"}, + {file = "tokenize_rt-4.1.0.tar.gz", hash = "sha256:ab339b5ff829eb5e198590477f9c03c84e762b3e455e74c018956e7e326cbc70"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +ujson = [ + {file = "ujson-4.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e390df0dcc7897ffb98e17eae1f4c442c39c91814c298ad84d935a3c5c7a32fa"}, + {file = "ujson-4.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:84b1dca0d53b0a8d58835f72ea2894e4d6cf7a5dd8f520ab4cbd698c81e49737"}, + {file = "ujson-4.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:91396a585ba51f84dc71c8da60cdc86de6b60ba0272c389b6482020a1fac9394"}, + {file = "ujson-4.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:eb6b25a7670c7537a5998e695fa62ff13c7f9c33faf82927adf4daa460d5f62e"}, + {file = "ujson-4.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f8aded54c2bc554ce20b397f72101737dd61ee7b81c771684a7dd7805e6cca0c"}, + {file = "ujson-4.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:30962467c36ff6de6161d784cd2a6aac1097f0128b522d6e9291678e34fb2b47"}, + {file = "ujson-4.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:fc51e545d65689c398161f07fd405104956ec27f22453de85898fa088b2cd4bb"}, + {file = "ujson-4.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e6e90330670c78e727d6637bb5a215d3e093d8e3570d439fd4922942f88da361"}, + {file = "ujson-4.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5e1636b94c7f1f59a8ead4c8a7bab1b12cc52d4c21ababa295ffec56b445fd2a"}, + {file = "ujson-4.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e2cadeb0ddc98e3963bea266cc5b884e5d77d73adf807f0bda9eca64d1c509d5"}, + {file = "ujson-4.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a214ba5a21dad71a43c0f5aef917cd56a2d70bc974d845be211c66b6742a471c"}, + {file = "ujson-4.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0190d26c0e990c17ad072ec8593647218fe1c675d11089cd3d1440175b568967"}, + {file = "ujson-4.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f273a875c0b42c2a019c337631bc1907f6fdfbc84210cc0d1fff0e2019bbfaec"}, + {file = "ujson-4.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d3a87888c40b5bfcf69b4030427cd666893e826e82cc8608d1ba8b4b5e04ea99"}, + {file = "ujson-4.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:7333e8bc45ea28c74ae26157eacaed5e5629dbada32e0103c23eb368f93af108"}, + {file = "ujson-4.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b3a6dcc660220539aa718bcc9dbd6dedf2a01d19c875d1033f028f212e36d6bb"}, + {file = "ujson-4.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0ea07fe57f9157118ca689e7f6db72759395b99121c0ff038d2e38649c626fb1"}, + {file = "ujson-4.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d6d061563470cac889c0a9fd367013a5dbd8efc36ad01ab3e67a57e56cad720"}, + {file = "ujson-4.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b5c70704962cf93ec6ea3271a47d952b75ae1980d6c56b8496cec2a722075939"}, + {file = "ujson-4.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:aad6d92f4d71e37ea70e966500f1951ecd065edca3a70d3861b37b176dd6702c"}, + {file = "ujson-4.0.2.tar.gz", hash = "sha256:c615a9e9e378a7383b756b7e7a73c38b22aeb8967a8bfbffd4741f7ffd043c4d"}, +] +uvicorn = [ + {file = "uvicorn-0.12.3-py3-none-any.whl", hash = "sha256:562ef6aaa8fa723ab6b82cf9e67a774088179d0ec57cb17e447b15d58b603bcf"}, + {file = "uvicorn-0.12.3.tar.gz", hash = "sha256:5836edaf4d278fe67ba0298c0537bdb6398cf359eb644f79e6500ca1aad232b3"}, +] +uvloop = [ + {file = "uvloop-0.15.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69"}, + {file = "uvloop-0.15.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7"}, + {file = "uvloop-0.15.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d"}, + {file = "uvloop-0.15.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c"}, + {file = "uvloop-0.15.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47"}, + {file = "uvloop-0.15.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c"}, + {file = "uvloop-0.15.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc"}, + {file = "uvloop-0.15.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760"}, + {file = "uvloop-0.15.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c"}, + {file = "uvloop-0.15.2.tar.gz", hash = "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01"}, +] +virtualenv = [ + {file = "virtualenv-20.4.6-py2.py3-none-any.whl", hash = "sha256:307a555cf21e1550885c82120eccaf5acedf42978fd362d32ba8410f9593f543"}, + {file = "virtualenv-20.4.6.tar.gz", hash = "sha256:72cf267afc04bf9c86ec932329b7e94db6a0331ae9847576daaa7ca3c86b29a4"}, +] +warlock = [ + {file = "warlock-1.3.3.tar.gz", hash = "sha256:a093c4d04b42b7907f69086e476a766b7639dca50d95edc83aef6aeab9db2090"}, +] +watchgod = [ + {file = "watchgod-0.6-py35.py36.py37-none-any.whl", hash = "sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a"}, + {file = "watchgod-0.6.tar.gz", hash = "sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +websockets = [ + {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"}, + {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"}, + {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"}, + {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"}, + {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"}, + {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"}, + {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"}, + {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"}, + {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, + {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, +] +werkzeug = [ + {file = "Werkzeug-2.0.1-py3-none-any.whl", hash = "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"}, + {file = "Werkzeug-2.0.1.tar.gz", hash = "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42"}, +] +win32-setctime = [ + {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, + {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, +] +wrapt = [ + {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, +] diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..ab1033b --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..79b0ca7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,97 @@ +[tool.poetry] +name = "skyline-apiserver" +version = "0.1.0" +description = "" +license = "Apache-2.0" +authors = ["OpenStack "] + +[tool.poetry.dependencies] +python = "^3.8" +fastapi = {extras = ["all"], version = "*"} +PyYAML = "*" +attrs = "*" +jsonschema = "*" +immutables = "*" +orjson = "*" +ujson = "*" +uvicorn = {extras = ["standard"], version = "^0.12.1"} +gunicorn = "*" +python-keystoneclient = "3.21.*" +python-cinderclient = "5.0.*" +python-glanceclient = "2.17.*" +python-heatclient = "1.18.*" +python-neutronclient = "6.14.*" +python-novaclient = "15.1.*" +python-octaviaclient = "1.10.*" +osc-placement = "1.7.*" +keystoneauth1 = "3.17.*" +email-validator = "^1.1.1" +python-jose = "^3.2.0" +passlib = "^1.7.2" +alembic = "^1.4.2" +bcrypt = "^3.2.0" +hiyapyco = "^0.4.16" +httpx = "^0.16.1" +sqlalchemy = "1.3.*" +databases = "*" +aiomysql = "^0.0.21" +pymysql = "*" +skyline-policy-manager = "*" +skyline-log = "*" + +[tool.poetry.dev-dependencies] +pytest = "6.1.*" +mypy = "*" +black = "^20.8b1" +isort = "*" +flake8 = "*" +add-trailing-comma = "*" +pre-commit = "*" +pyperf = "*" +pympler = "*" +bandit = "^1.6.2" +aiosqlite = "*" +asgi-lifespan = "*" +pytest-asyncio = "*" +pytest-xdist = {extras = ["psutil"], version = "*"} +skyline-policy-manager = {path = "./libs/skyline-policy-manager", develop = true} +skyline-log = {path = "./libs/skyline-log", develop = true} + +[tool.poetry.scripts] +swagger-generator = 'skyline_apiserver.cmd.generate_swagger:main' + +[tool.black] +line-length = 98 +target-version = ['py38'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + )/ + | exclude.py +) +''' +verbos = true + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +line_length = 98 +reverse_relative = true +combine_as_imports = true + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/src/skyline_apiserver/__init__.py b/src/skyline_apiserver/__init__.py new file mode 100644 index 0000000..0b747ca --- /dev/null +++ b/src/skyline_apiserver/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "0.1.0" diff --git a/src/skyline_apiserver/__main__.py b/src/skyline_apiserver/__main__.py new file mode 100644 index 0000000..d5eb3c3 --- /dev/null +++ b/src/skyline_apiserver/__main__.py @@ -0,0 +1,34 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import asyncio +from logging import StreamHandler +from pprint import pprint + +import uvloop +from skyline_log import setup + +from skyline_apiserver.config import configure + + +async def main() -> None: + configure("skyline-apiserver") + setup(StreamHandler()) + pprint("Run some debug code") + + +uvloop.install() +asyncio.run(main()) diff --git a/src/skyline_apiserver/api/__init__.py b/src/skyline_apiserver/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/api/deps.py b/src/skyline_apiserver/api/deps.py new file mode 100644 index 0000000..d7c6262 --- /dev/null +++ b/src/skyline_apiserver/api/deps.py @@ -0,0 +1,83 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import time +from typing import Optional + +import jose +from fastapi import HTTPException, Request, Response, status +from fastapi.security import APIKeyCookie + +from skyline_apiserver import schemas +from skyline_apiserver.config import CONF +from skyline_apiserver.core.security import generate_profile_by_token, parse_access_token +from skyline_apiserver.db import api as db_api +from skyline_apiserver.types import constants + + +class TokenCookie(APIKeyCookie): + async def __call__(self, request: Request) -> Optional[str]: + api_key = request.cookies.get(self.model.name) + if not api_key: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=constants.ERR_MSG_TOKEN_NOTFOUND, + ) + return api_key + + +async def getJWTPayload(request: Request) -> (str): + token = request.cookies.get(CONF.default.session_name) + return token + + +async def get_profile(request: Request) -> schemas.Profile: + payload = await TokenCookie(name=CONF.default.session_name)(request) + try: + await db_api.purge_revoked_token() + token = parse_access_token(payload) + is_revoked = await db_api.check_token(token.uuid) + if is_revoked: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=constants.ERR_MSG_TOKEN_REVOKED, + ) + profile = await generate_profile_by_token(token) + except HTTPException as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=e.detail, + ) + except jose.exceptions.ExpiredSignatureError as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=constants.ERR_MSG_TOKEN_EXPIRED + " : " + str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + return profile + + +async def get_profile_update_jwt(request: Request, response: Response) -> schemas.Profile: + profile = await get_profile(request) + + if 0 < profile.exp - time.time() < CONF.default.access_token_renew: + profile.exp = int(time.time()) + CONF.default.access_token_expire + response.set_cookie(CONF.default.session_name, profile.toJWTPayload()) + return profile diff --git a/src/skyline_apiserver/api/v1/__init__.py b/src/skyline_apiserver/api/v1/__init__.py new file mode 100644 index 0000000..f1b5563 --- /dev/null +++ b/src/skyline_apiserver/api/v1/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from fastapi import APIRouter + +from skyline_apiserver.api.v1 import contrib, extension, login, policy, setting + +api_router = APIRouter() +api_router.include_router(login.router, tags=["Login"]) +api_router.include_router(extension.router, tags=["Extension"]) +api_router.include_router(contrib.router, tags=["Contrib"]) +api_router.include_router(policy.router, tags=["Policy"]) +api_router.include_router(setting.router, tags=["Setting"]) diff --git a/src/skyline_apiserver/api/v1/contrib.py b/src/skyline_apiserver/api/v1/contrib.py new file mode 100644 index 0000000..bfc21e4 --- /dev/null +++ b/src/skyline_apiserver/api/v1/contrib.py @@ -0,0 +1,112 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import asyncio +from typing import Any, List + +from fastapi import APIRouter, HTTPException, status +from skyline_log import LOG + +from skyline_apiserver import schemas +from skyline_apiserver.client.openstack import system +from skyline_apiserver.client.openstack.system import get_endpoints +from skyline_apiserver.config import CONF +from skyline_apiserver.schemas import common + +router = APIRouter() + + +@router.get( + "/contrib/keystone_endpoints", + description="List Keystone Endpoints", + responses={ + 200: {"model": List[schemas.ContribListKeystoneEndpointsResponseModel]}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=List[schemas.ContribListKeystoneEndpointsResponseModel], + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_keystone_endpoints() -> List[schemas.ContribListKeystoneEndpointsResponseModel]: + """Contrib List Keystone Endpoints.""" + try: + regions = await system.get_regions() + tasks = [asyncio.create_task(get_endpoints(region)) for region in regions] + endpoints = await asyncio.gather(*tasks) + result = [ + {"region_name": region, "url": endpoint.get("keystone")} + for region, endpoint in zip(regions, endpoints) + ] + return result + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +@router.get( + "/contrib/domains", + description="List Domains", + responses={ + 200: {"model": List[str]}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=List[str], + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_domains() -> Any: + """Contrib List Domain Names.""" + + try: + regions = await system.get_regions() + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + for region in regions: + try: + domains = await system.get_domains(region) + return [domain for domain in domains if domain not in CONF.openstack.base_domains] + except Exception as e: + LOG.warning(str(e)) + continue + return [] + + +@router.get( + "/contrib/regions", + description="List Regions", + responses={ + 200: {"model": List[str]}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=List[str], + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_regions() -> Any: + """Contrib List Regions.""" + try: + return await system.get_regions() + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) diff --git a/src/skyline_apiserver/api/v1/extension.py b/src/skyline_apiserver/api/v1/extension.py new file mode 100644 index 0000000..0fb13da --- /dev/null +++ b/src/skyline_apiserver/api/v1/extension.py @@ -0,0 +1,1052 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import asyncio +import math +from asyncio import gather +from functools import reduce +from typing import List + +from dateutil import parser +from fastapi import APIRouter, Depends, HTTPException, Query, status + +from skyline_apiserver import schemas +from skyline_apiserver.api import deps +from skyline_apiserver.api.v1.openstack.base import OSPort, OSServer, OSVolume, OSVolumeSnapshot +from skyline_apiserver.api.v1.utils import Port, Server, Service, Volume, VolumeSnapshot +from skyline_apiserver.client import utils +from skyline_apiserver.client.openstack import cinder, glance, keystone, neutron, nova +from skyline_apiserver.client.utils import generate_session, get_endpoint, get_system_session +from skyline_apiserver.config import CONF +from skyline_apiserver.network.neutron import get_ports +from skyline_apiserver.schemas import common +from skyline_apiserver.types import constants +from skyline_apiserver.utils.roles import assert_system_admin_or_reader, is_system_reader_no_admin + +router = APIRouter() + +STEP = constants.ID_UUID_RANGE_STEP + + +@router.get( + "/extension/servers", + description=""" +List Servers. + +*Notes*: +- The `host` of **sort_keys** is only used for admin/system_admin role users. +- The `name` is to support for fuzzy queries. +""", + responses={ + 200: {"model": schemas.ExtListServersResponse}, + 400: {"model": common.BadRequestMessage}, + 401: {"model": common.UnauthorizedMessage}, + 403: {"model": common.ForbiddenMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=schemas.ExtListServersResponse, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_servers( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), + limit: int = Query(None, gt=constants.EXTENSION_API_LIMIT_GT), + marker: str = None, + sort_dirs: schemas.ExtSortDir = None, + sort_keys: List[schemas.ExtServerSortKey] = Query(None), + all_projects: bool = None, + project_id: str = Query( + None, + description="Only works when the all_projects filter is also specified.", + ), + project_name: str = Query( + None, + description="Only works when the all_projects filter is also specified.", + ), + name: str = None, + status: schemas.ExtServerStatus = None, + host: str = Query(None, description="It will be ignored for non-admin user."), + flavor_id: str = None, + uuid: str = Query(None, description="UUID of server."), +) -> schemas.ExtListServersResponse: + """Extension List Servers. + + :param profile: Profile object include token, role and so on, + defaults to Depends(deps.get_profile_update_jwt) + :type profile: schemas.Profile, optional + :param limit: Limit count to fetch, + defaults to Query(None, gt=constants.EXTENSION_API_LIMIT_GT) + :type limit: int, optional + :param marker: Marker object to fetch, defaults to None + :type marker: str, optional + :param sort_dirs: Sort order, defaults to None + :type sort_dirs: schemas.ExtSortDir, optional + :param sort_keys: Sort keys, defaults to Query(None) + :type sort_keys: List[schemas.ExtServerSortKey], optional + :param all_projects: All projects to fetch, defaults to None + :type all_projects: bool, optional + :param project_id: Filter by id of project which server belongs to, + defaults to Query(None, description=" + Only works when the all_projects filter is also specified.") + :type project_id: str, optional + :param project_name: Filter by name of project which server belongs to, + defaults to Query(None, description=" + Only works when the all_projects filter is also specified.") + :type project_name: str, optional + :param name: Filter by server name, defaults to None + :type name: str, optional + :param status: Filter by server status, defaults to None + :type status: schemas.ExtServerStatus, optional + :param host: Filter by host which server is located at, + defaults to Query(None, description="It will be ignored for non-admin user.") + :type host: str, optional + :param flavor_id: Filter by id of flavor which server is created by, defaults to None + :type flavor_id: str, optional + :param uuid: Filter by uuid, defaults to Query(None, description="UUID of server.") + :type uuid: str, optional + :raises HTTPException: HTTP Exception + :return: Server List + :rtype: schemas.ExtListServersResponse + """ + if all_projects: + assert_system_admin_or_reader( + profile=profile, + exception="Not allowed to get servers for all projects.", + ) + + current_session = await generate_session(profile=profile) + system_session = get_system_session() + + # Check first if we supply the project_name filter. + if project_name: + filter_projects = await keystone.list_projects( + profile=profile, + all_projects=all_projects, + session=current_session, + search_opts={"name": project_name}, + ) + if not filter_projects: + return {"servers": []} + else: + # Projects will not have the same name or same id in the same domain + filter_project = filter_projects[0] + # When we both supply the project_id and project_name filter, if the project's id does + # not equal the project_id, just return []. + if project_id and filter_project.id != project_id: + return {"servers": []} + project_id = filter_project.id + + search_opts = { + "name": name, + "status": status, + "host": host, + "flavor": flavor_id, + "project_id": project_id, + "all_tenants": all_projects, + "uuid": uuid, + } + servers = await nova.list_servers( + profile=profile, + session=current_session, + search_opts=search_opts, + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=[sort_dirs] if sort_dirs else None, + ) + + result = [] + server_ids = [] + image_ids = [] + root_device_ids = [] + for server in servers: + origin_data = OSServer(server).to_dict() + server = Server(server).to_dict() + server["origin_data"] = origin_data + result.append(server) + server_ids.append(server["id"]) + if server["image"] and server["image"] not in image_ids: + image_ids.append(server["image"]) + for volume_attached in server["volumes_attached"]: + root_device_ids.append(volume_attached["id"]) + + # We will use system session when we use all projects and the role + # of use is system_reader but no admin/system_admin role. + cinder_session = ( + system_session + if all_projects and is_system_reader_no_admin(profile=profile) + else current_session + ) + + if all_projects: + tasks = [ + keystone.list_projects( + profile=profile, + all_projects=all_projects, + session=current_session, + ), + ] + else: + tasks = [asyncio.sleep(0.01)] + + for i in range(0, len(image_ids), STEP): + tasks.append( + glance.list_images( + profile=profile, + session=system_session, + filters={"id": "in:" + ",".join(image_ids[i : i + STEP])}, + ), + ) + root_device_ids = list(set(root_device_ids)) + for i in range(0, len(root_device_ids), STEP): + tasks.append( + cinder.list_volumes( + profile=profile, + session=cinder_session, + search_opts={"id": root_device_ids[i : i + STEP], "all_tenants": all_projects}, + ), + ) + task_result = await gather(*tasks) + + projects = task_result[0] if task_result[0] else [] + proj_mappings = {project.id: project.name for project in projects} + total_image_tasks = math.ceil(len(image_ids) / STEP) + images = reduce(lambda x, y: list(x) + list(y), task_result[1 : 1 + total_image_tasks], []) + volumes = reduce(lambda x, y: x + y, task_result[1 + total_image_tasks :], []) + image_mappings = { + image.id: {"name": image.name, "image_os_distro": getattr(image, "os_distro", None)} + for image in list(images) + } + ser_image_mappings = {} + for volume in volumes: + image_meta = getattr(volume, "volume_image_metadata", None) + for attachment in volume.attachments: + if image_meta: + ser_image_mappings[attachment["server_id"]] = { + "image": image_meta.get("image_id"), + "image_name": image_meta.get("image_name"), + "image_os_distro": image_meta.get("os_distro"), + } + + for server in result: + server["host"] = server["host"] if all_projects else None + server["project_name"] = proj_mappings.get(server["project_id"]) + ser_image_mapping = ser_image_mappings.get(server["id"]) + if ser_image_mapping: + values = { + "image": ser_image_mapping["image"], + "image_name": ser_image_mapping["image_name"], + "image_os_distro": ser_image_mapping["image_os_distro"], + } + elif server["image"]: + values = { + "image": server["image"], + "image_name": image_mappings.get(server["image"], {}).get("name", ""), + "image_os_distro": image_mappings.get(server["image"], {}).get( + "image_os_distro", + "", + ), + } + else: + values = {"image": None, "image_name": None, "image_os_distro": None} + server.update(values) + return {"servers": result} + + +@router.get( + "/extension/recycle_servers", + description=""" +List Recycle Servers. + +*Notes*: +- The `updated_at` of **sort_keys** is used as `deleted_at`. +- The `name` is to support for fuzzy queries. +""", + responses={ + 200: {"model": schemas.ExtListRecycleServersResponse}, + 400: {"model": common.BadRequestMessage}, + 401: {"model": common.UnauthorizedMessage}, + 403: {"model": common.ForbiddenMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=schemas.ExtListRecycleServersResponse, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_recycle_servers( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), + limit: int = Query(None, gt=constants.EXTENSION_API_LIMIT_GT), + marker: str = None, + sort_dirs: schemas.ExtSortDir = None, + sort_keys: List[schemas.ExtRecycleServerSortKey] = Query(None), + all_projects: bool = None, + project_id: str = Query( + None, + description="Only works when the all_projects filter is also specified.", + ), + project_name: str = Query( + None, + description="Only works when the all_projects filter is also specified.", + ), + name: str = None, + uuid: str = Query(None, description="UUID of recycle server."), +) -> schemas.ExtListRecycleServersResponse: + """Extension List Recycle Servers. + + :param profile: Profile object include token, role and so on, + defaults to Depends(deps.get_profile_update_jwt) + :type profile: schemas.Profile, optional + :param limit: Limit count to fetch, + defaults to Query(None, gt=constants.EXTENSION_API_LIMIT_GT) + :type limit: int, optional + :param marker: Marker object to fetch, defaults to None + :type marker: str, optional + :param sort_dirs: Sort order, defaults to None + :type sort_dirs: schemas.ExtSortDir, optional + :param sort_keys: Sort keys, defaults to Query(None) + :type sort_keys: List[schemas.ExtServerSortKey], optional + :param all_projects: All projects to fetch, defaults to None + :type all_projects: bool, optional + :param project_id: Filter by id of project which recycle server belongs to, + defaults to Query(None, description=" + Only works when the all_projects filter is also specified.") + :type project_id: str, optional + :param project_name: Filter by name of project which server belongs to, + defaults to Query(None, description=" + Only works when the all_projects filter is also specified.") + :type project_name: str, optional + :param name: Filter by recycle server name, defaults to None + :type name: str, optional + :param uuid: Filter by uuid, + defaults to Query(None, description="UUID of recycle server.") + :type uuid: str, optional + :raises HTTPException: HTTP Exception + :return: Recycle server list + :rtype: schemas.ExtListRecycleServersResponse + """ + + if all_projects: + assert_system_admin_or_reader( + profile=profile, + exception="Not allowed to get recycle servers for all projects.", + ) + + current_session = await generate_session(profile=profile) + system_session = get_system_session() + + # Check first if we supply the project_name filter. + if project_name: + filter_projects = await keystone.list_projects( + profile=profile, + all_projects=all_projects, + session=current_session, + search_opts={"name": project_name}, + ) + if not filter_projects: + return {"recycle_servers": []} + else: + # Projects will not have the same name or same id in the same domain + filter_project = filter_projects[0] + # When we both supply the project_id and project_name filter, if the project's id does + # not equal the project_id, just return []. + if project_id and filter_project.id != project_id: + return {"recycle_servers": []} + project_id = filter_project.id + + search_opts = { + "status": "soft_deleted", + "deleted": True, + "all_tenants": True, + "name": name, + "project_id": project_id, + "uuid": uuid, + } + if not all_projects: + search_opts["tenant_id"] = profile.project.id + servers = await nova.list_servers( + profile=profile, + session=system_session, + search_opts=search_opts, + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=[sort_dirs] if sort_dirs else None, + ) + + result = [] + server_ids = [] + image_ids = [] + root_device_ids = [] + for server in servers: + origin_data = OSServer(server).to_dict() + server = Server(server).to_dict() + server["origin_data"] = origin_data + result.append(server) + server_ids.append(server["id"]) + if server["image"] and server["image"] not in image_ids: + image_ids.append(server["image"]) + for volume_attached in server["volumes_attached"]: + root_device_ids.append(volume_attached["id"]) + + cinder_session = ( + system_session + if all_projects and is_system_reader_no_admin(profile=profile) + else current_session + ) + + if all_projects: + tasks = [ + keystone.list_projects( + profile=profile, + all_projects=all_projects, + session=current_session, + ), + ] + else: + tasks = [asyncio.sleep(0.01)] + + for i in range(0, len(image_ids), STEP): + tasks.append( + glance.list_images( + profile=profile, + session=system_session, + filters={"id": "in:" + ",".join(image_ids[i : i + STEP])}, + ), + ) + root_device_ids = list(set(root_device_ids)) + for i in range(0, len(root_device_ids), STEP): + tasks.append( + cinder.list_volumes( + profile=profile, + session=cinder_session, + search_opts={"id": root_device_ids[i : i + STEP], "all_tenants": all_projects}, + ), + ) + task_result = await gather(*tasks) + + projects = task_result[0] if task_result[0] else [] + proj_mappings = {project.id: project.name for project in projects} + total_image_tasks = math.ceil(len(image_ids) / STEP) + images = reduce(lambda x, y: list(x) + list(y), task_result[1 : 1 + total_image_tasks], []) + volumes = reduce(lambda x, y: x + y, task_result[1 + total_image_tasks :], []) + image_mappings = { + image.id: {"name": image.name, "image_os_distro": getattr(image, "os_distro", None)} + for image in list(images) + } + ser_image_mappings = {} + for volume in volumes: + image_meta = getattr(volume, "volume_image_metadata", None) + for attachment in volume.attachments: + if image_meta: + ser_image_mappings[attachment["server_id"]] = { + "image": image_meta.get("image_id"), + "image_name": image_meta.get("image_name"), + "image_os_distro": image_meta.get("os_distro"), + } + + for recycle_server in result: + recycle_server["host"] = recycle_server["host"] if all_projects else None + recycle_server["project_name"] = proj_mappings.get(recycle_server["project_id"]) + recycle_server["deleted_at"] = recycle_server["updated_at"] + recycle_server["reclaim_timestamp"] = ( + parser.isoparse(recycle_server["updated_at"]).timestamp() + + CONF.openstack.reclaim_instance_interval + ) + ser_image_mapping = ser_image_mappings.get(recycle_server["id"]) + if ser_image_mapping: + values = { + "image": ser_image_mapping["image"], + "image_name": ser_image_mapping["image_name"], + "image_os_distro": ser_image_mapping["image_os_distro"], + } + elif recycle_server["image"]: + values = { + "image": recycle_server["image"], + "image_name": image_mappings.get(recycle_server["image"], {}).get("name", ""), + "image_os_distro": image_mappings.get(recycle_server["image"], {}).get( + "image_os_distro", + "", + ), + } + else: + values = {"image": None, "image_name": None, "image_os_distro": None} + recycle_server.update(values) + return {"recycle_servers": result} + + +@router.get( + "/extension/volumes", + description="List Volumes.", + responses={ + 200: {"model": schemas.ExtListVolumesResponse}, + 401: {"model": common.UnauthorizedMessage}, + 403: {"model": common.ForbiddenMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=schemas.ExtListVolumesResponse, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_volumes( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), + limit: int = Query(None, gt=constants.EXTENSION_API_LIMIT_GT), + marker: str = None, + sort_dirs: schemas.ExtSortDir = None, + sort_keys: List[schemas.ExtVolumeSortKey] = Query(None), + all_projects: bool = None, + project_id: str = None, + name: str = None, + multiattach: bool = None, + status: schemas.ExtVolumeStatus = None, + bootable: bool = None, + uuid: List[str] = Query(None, description="UUID of volume."), +) -> schemas.ExtListVolumesResponse: + """Extension List Volumes. + + :param profile: Profile object include token, role and so on, + defaults to Depends(deps.get_profile_update_jwt) + :type profile: schemas.Profile, optional + :param limit: Limit count to fetch, + defaults to Query(None, gt=constants.EXTENSION_API_LIMIT_GT) + :type limit: int, optional + :param marker: Marker object to fetch, defaults to None + :type marker: str, optional + :param sort_dirs: Sort order, defaults to None + :type sort_dirs: schemas.ExtSortDir, optional + :param sort_keys: Sort keys, defaults to Query(None) + :type sort_keys: List[schemas.ExtServerSortKey], optional + :param all_projects: All projects to fetch, defaults to None + :type all_projects: bool, optional + :param project_id: Filter by id of project which volume belongs to, + defaults to None + :type project_id: str, optional + :param name: Filter by volume name, defaults to None + :type name: str, optional + :param multiattach: Filter by multiattach that server is support multiattach or not, + defaults to None + :type multiattach: bool, optional + :param status: Filter by volume status, defaults to None + :type status: schemas.ExtVolumeStatus, optional + :type bootable: Filter by bootable that server be used to create an instance quickly. + :type bootable: bool, optional + :param uuid: Filter by list uuid, + defaults to Query(None, description="UUID of volume.") + :type uuid: List[str], optional + :return: Volume list + :rtype: schemas.ExtListVolumesResponse + """ + if all_projects: + assert_system_admin_or_reader( + profile=profile, + exception="Not allowed to get volumes for all projects.", + ) + + current_session = await generate_session(profile=profile) + system_session = get_system_session() + + sort = None + if sort_keys: + if sort_dirs: + sort = ",".join([f"{sort_key}:{sort_dirs}" for sort_key in sort_keys]) + else: + sort = ",".join(sort_keys) + + # If bootable is false, it is ineffective, due to existed issue of community + # https://bugs.launchpad.net/python-cinderclient/+bug/1925737 + search_opts = { + "with_count": True, + "name": name, + "multiattach": multiattach, + "status": status, + "all_tenants": all_projects, + "project_id": project_id, + "bootable": bootable, + "id": uuid, + } + # if not is_admin, cinder will ignore the all_projects query param. + # role:admin or role:cinder_system_admin is is_admin. + # so here we just use skyline session to get all_projects' volumes. + cinder_session = ( + system_session + if all_projects and is_system_reader_no_admin(profile=profile) + else current_session + ) + volumes, count = await cinder.list_volumes( + profile=profile, + session=cinder_session, + limit=limit, + marker=marker, + search_opts=search_opts, + sort=sort, + ) + result = [] + server_ids = [] + # commit: https://review.opendev.org/c/openstack/python-cinderclient/+/767451 + # here is just a workaround way. + while True: + if volumes and isinstance(volumes[-1], int): + volumes = volumes[0] + else: + break + for volume in volumes: + origin_data = OSVolume(volume).to_dict() + volume = Volume(volume).to_dict() + volume["origin_data"] = origin_data + result.append(volume) + for attachment in volume["attachments"]: + if attachment["server_id"] not in server_ids: + server_ids.append(attachment["server_id"]) + + # Sometimes, the servers have been soft deleted, but the volumes will + # be still displayed on the volume page. If we do not get the recycle + # servers, the attachment server name for those volumes which are attached + # to these servers will be blank. + if all_projects: + tasks = [ + keystone.list_projects( + profile=profile, + all_projects=all_projects, + session=current_session, + ), + ] + else: + tasks = [asyncio.sleep(0.01)] + # We should split the server_ids with 100 number. + # If we do not do this, the length of url will be too long to do request. + server_ids = list(set(server_ids)) + for i in range(0, len(server_ids), STEP): + tasks.extend( + [ + nova.list_servers( + profile=profile, + session=current_session, + search_opts={ + "uuid": server_ids[i : i + STEP], + "all_tenants": all_projects, + }, + ), + nova.list_servers( + profile=profile, + session=current_session, + search_opts={ + "uuid": server_ids[i : i + STEP], + "status": "soft_deleted", + "deleted": True, + "all_tenants": all_projects, + }, + ), + ], + ) + task_result = await gather(*tasks) + + projects = [] if not task_result[0] else task_result[0] + servers = reduce(lambda x, y: x + y, task_result[1:], []) + proj_mappings = {project.id: project.name for project in projects} + ser_mappings = {server.id: server.name for server in servers} + + for volume in result: + volume["host"] = volume["host"] if all_projects else None + volume["project_name"] = proj_mappings.get(volume["project_id"]) + for attachment in volume["attachments"]: + attachment["server_name"] = ser_mappings.get(attachment["server_id"]) + return {"count": count, "volumes": result} + + +@router.get( + "/extension/volume_snapshots", + description="List Volume Snapshots.", + responses={ + 200: {"model": schemas.ExtListVolumeSnapshotsResponse}, + 401: {"model": common.UnauthorizedMessage}, + 403: {"model": common.ForbiddenMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=schemas.ExtListVolumeSnapshotsResponse, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_volume_snapshots( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), + limit: int = Query(None, gt=constants.EXTENSION_API_LIMIT_GT), + marker: str = None, + sort_dirs: schemas.ExtSortDir = None, + sort_keys: List[schemas.ExtVolumeSnapshotSortKey] = Query(None), + all_projects: bool = None, + project_id: str = None, + name: str = None, + status: schemas.ExtVolumeSnapshotStatus = None, + volume_id: str = None, +) -> schemas.ExtListVolumeSnapshotsResponse: + """Extension List Volume Snapshots. + + :param profile: Profile object include token, role and so on, + defaults to Depends(deps.get_profile_update_jwt) + :type profile: schemas.Profile, optional + :param limit: Limit count to fetch, + defaults to Query(None, gt=constants.EXTENSION_API_LIMIT_GT) + :type limit: int, optional + :param marker: Marker object to fetch, defaults to None + :type marker: str, optional + :param sort_dirs: Sort order, defaults to None + :type sort_dirs: schemas.ExtSortDir, optional + :param sort_keys: Sort keys, defaults to Query(None) + :type sort_keys: List[schemas.ExtServerSortKey], optional + :param all_projects: All projects to fetch, defaults to None + :type all_projects: bool, optional + :param project_id: Filter by id of project which volume snapshots belongs to, + defaults to None + :type project_id: str, optional + :param name: Filter by volume snapshot name, defaults to None + :type name: str, optional + :param status: Filter by volume snapshot status, defaults to None + :type status: schemas.ExtVolumeSnapshotStatus, optional + :param volume_id: Filter by volume id, defaults to None + :type volume_id: str, optional + :return: Volume snapshot list + :rtype: schemas.ExtListVolumeSnapshotsResponse + """ + if all_projects: + assert_system_admin_or_reader( + profile=profile, + exception="Not allowed to get volume snapshots for all projects.", + ) + + current_session = await generate_session(profile=profile) + + sort = None + if sort_keys: + if sort_dirs: + sort = ",".join([f"{sort_key}:{sort_dirs}" for sort_key in sort_keys]) + else: + sort = ",".join(sort_keys) + search_opts = { + "with_count": True, + "name": name, + "status": status, + "volume_id": volume_id, + "all_tenants": all_projects, + "project_id": project_id, + } + volume_snapshots, count = await cinder.list_volume_snapshots( + profile=profile, + session=current_session, + limit=limit, + marker=marker, + search_opts=search_opts, + sort=sort, + ) + result = [] + volume_ids = [] + snapshot_ids = [] + # commit: https://review.opendev.org/c/openstack/python-cinderclient/+/767451 + # here is just a workaround way. + while True: + if volume_snapshots and isinstance(volume_snapshots[-1], int): + volume_snapshots = volume_snapshots[0] + else: + break + for volume_snapshot in volume_snapshots: + origin_data = OSVolumeSnapshot(volume_snapshot).to_dict() + volume_snapshot = VolumeSnapshot(volume_snapshot).to_dict() + volume_snapshot["origin_data"] = origin_data + result.append(volume_snapshot) + volume_ids.append(volume_snapshot["volume_id"]) + snapshot_ids.append(volume_snapshot["id"]) + + if all_projects: + tasks = [ + keystone.list_projects( + profile=profile, + all_projects=all_projects, + session=current_session, + ), + ] + else: + tasks = [asyncio.sleep(0.01)] + + volume_ids = list(set(volume_ids)) + for i in range(0, len(volume_ids), STEP): + tasks.append( + cinder.list_volumes( + profile=profile, + session=current_session, + search_opts={"id": volume_ids[i : i + STEP], "all_tenants": all_projects}, + ), + ) + for i in range(0, len(snapshot_ids), STEP): + tasks.append( + cinder.list_volumes( + profile=profile, + session=current_session, + search_opts={ + "snapshot_id": snapshot_ids[i : i + STEP], + "all_tenants": all_projects, + }, + ), + ) + task_result = await gather(*tasks) + + projects = task_result[0] if task_result[0] else [] + total_volume_tasks = math.ceil(len(volume_ids) / STEP) + volumes = reduce(lambda x, y: x + y, task_result[1 : 1 + total_volume_tasks], []) + volumes_from_snapshot = reduce(lambda x, y: x + y, task_result[1 + total_volume_tasks :], []) + + proj_mappings = {project.id: project.name for project in projects} + vol_mappings = {} + for volume in volumes: + vol_mappings[volume.id] = { + "name": volume.name, + "host": getattr(volume, "os-vol-host-attr:host", None), + } + child_volumes = {} + for volume in volumes_from_snapshot: + child_volumes.setdefault(volume.snapshot_id, []) + child_volumes[volume.snapshot_id].append(volume.name) + + for snapshot in result: + snapshot["project_name"] = proj_mappings.get(snapshot["project_id"]) + vol_mapping = vol_mappings.get(snapshot["volume_id"]) + if vol_mapping: + snapshot["volume_name"] = vol_mapping["name"] + snapshot["host"] = vol_mapping["host"] if all_projects else None + snapshot["child_volumes"] = child_volumes.get(snapshot["id"], []) + return {"count": count, "volume_snapshots": result} + + +@router.get( + "/extension/ports", + description="List Ports.", + responses={ + 200: {"model": schemas.ExtListPortsResponse}, + 401: {"model": common.UnauthorizedMessage}, + 403: {"model": common.ForbiddenMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=schemas.ExtListPortsResponse, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_ports( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), + limit: int = Query(None, gt=constants.EXTENSION_API_LIMIT_GT), + marker: str = None, + sort_dirs: schemas.ExtSortDir = None, + sort_keys: List[schemas.ExtPortSortKey] = Query(None), + all_projects: bool = None, + project_id: str = None, + name: str = None, + status: schemas.ExtPortStatus = None, + network_name: str = None, + network_id: str = None, + device_id: str = None, + device_owner: List[schemas.ExtPortDeviceOwner] = Query(None), + uuid: List[str] = Query(None, description="UUID of port."), +) -> schemas.ExtListPortsResponse: + """Extension List Ports. + + :param profile: Profile object include token, role and so on, + defaults to Depends(deps.get_profile_update_jwt) + :type profile: schemas.Profile, optional + :param limit: Limit count to fetch, + defaults to Query(None, gt=constants.EXTENSION_API_LIMIT_GT) + :type limit: int, optional + :param marker: Marker object to fetch, defaults to None + :type marker: str, optional + :param sort_dirs: Sort order, defaults to None + :type sort_dirs: schemas.ExtSortDir, optional + :param sort_keys: Sort keys, defaults to Query(None) + :type sort_keys: List[schemas.ExtServerSortKey], optional + :param all_projects: All projects to fetch, defaults to None + :type all_projects: bool, optional + :param project_id: Filter by id of project which ports belongs to, + defaults to None + :type project_id: str, optional + :param name: Filter by port name, defaults to None + :type name: str, optional + :param status: Filter by port status, defaults to None + :type status: schemas.ExtPortStatus, optional + :param network_name: Filter by name of network, defaults to None + :type network_name: str, optional + :param network_id: Filter by id of network, defaults to None + :type network_id: str, optional + :param device_id: Filter by id of device, defaults to None + :type device_id: str, optional + :param device_owner: Filter by device owner, defaults to Query(None) + :type device_owner: List[schemas.ExtPortDeviceOwner], optional + :param uuid: Filter by list uuid, + defaults to Query(None, description="UUID of port.") + :type uuid: List[str], optional + :return: Port list + :rtype: schemas.ExtListPortsResponse + """ + current_session = await generate_session(profile=profile) + + kwargs = {} + if limit is not None: + kwargs["limit"] = limit + if marker is not None: + kwargs["marker"] = marker + if not all_projects: + kwargs["project_id"] = profile.project.id + if project_id is not None: + kwargs["project_id"] = project_id + if name is not None: + kwargs["name"] = name + if status is not None: + kwargs["status"] = status + if device_owner is not None: + kwargs["device_owner"] = device_owner + if uuid is not None: + kwargs["id"] = set(uuid) + if network_name is not None: + networks = await neutron.list_networks( + profile=profile, + session=current_session, + **{"name": network_name}, + ) + if not networks["networks"]: + return {"ports": []} + network_ids = [network["id"] for network in networks["networks"]] + kwargs["network_id"] = network_ids + if network_id is not None: + network_ids = kwargs.get("network_id", []) + if network_ids and network_id not in network_ids: + return {"ports": []} + elif not network_ids: + network_ids.append(network_id) + kwargs["network_id"] = network_ids + if device_id is not None: + kwargs["device_id"] = device_id + if sort_keys is not None: + sort_dir = [] + sort_dir.extend([sort_dirs if sort_dirs is not None else "asc"] * len(sort_keys)) + kwargs["sort_dir"] = sort_dir + kwargs["sort_key"] = sort_keys + + try: + neutron_endpoint = await get_endpoint(profile.region, "neutron", current_session) + except Exception as ex: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(ex)) + + ports = await get_ports(neutron_endpoint, profile.keystone_token, kwargs) + ports_count = ports.get("count", 0) + ports = ports["ports"] + + server_ids = [] + network_ids = [] + result = [] + for port in ports: + origin_data = OSPort(port).to_dict() + port = Port(port).to_dict() + port["origin_data"] = origin_data + result.append(port) + if port["device_owner"] == "compute:nova": + server_ids.append(port["device_id"]) + network_ids.append(port["network_id"]) + + network_params = {} + tasks = [ + neutron.list_networks(profile=profile, session=current_session, **{"shared": True}), + ] + if not all_projects: + network_params["project_id"] = profile.project.id + network_ids = list(set(network_ids)) + # We should split the network_ids with 100 number. + # If we do not do this, the length of url will be too long to do request. + for i in range(0, len(network_ids), STEP): + network_params["id"] = set(network_ids[i : i + STEP]) + tasks.append( + neutron.list_networks(profile=profile, session=current_session, **network_params), + ) + + # We should split the server_ids with 100 number. + # If we do not do this, the length of url will be too long to do request. + server_ids = list(set(server_ids)) + for i in range(0, len(server_ids), STEP): + tasks.append( + nova.list_servers( + profile=profile, + session=current_session, + search_opts={ + "uuid": server_ids[i : i + STEP], + "all_tenants": all_projects, + }, + ), + ) + task_result = await gather(*tasks) + + total_network_tasks = math.ceil(len(network_ids) / STEP) + servers = reduce(lambda x, y: x + y, task_result[1 + total_network_tasks :], []) + ser_mappings = {server.id: server.name for server in servers} + _networks = [net.get("networks", []) for net in task_result[1 : 1 + total_network_tasks]] + shared_nets = task_result[0].get("networks", []) + nets = reduce(lambda x, y: x + y, _networks, []) + shared_nets + network_mappings = {net["id"]: net["name"] for net in nets} + for port in result: + port["server_name"] = ser_mappings.get(port["device_id"]) + port["network_name"] = network_mappings.get(port["network_id"]) + return {"count": ports_count, "ports": result} + + +@router.get( + "/extension/compute-services", + description="List compute services.", + responses={ + 200: {"model": schemas.ExtListComputeServicesResponse}, + 401: {"model": common.UnauthorizedMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=schemas.ExtListComputeServicesResponse, + status_code=status.HTTP_200_OK, + response_description="OK", + response_model_exclude_none=True, +) +async def compute_services( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), + binary: str = None, + host: str = None, +) -> schemas.ExtListComputeServicesResponse: + """Extension List Compute Services. + + :param profile: Profile object include token, role and so on, + defaults to Depends(deps.get_profile_update_jwt) + :type profile: schemas.Profile, optional + :param binary: Filter by service binary name, defaults to None + :type binary: str, optional + :param host: Filter by host name, defaults to None + :type host: str, optional + :return: Compute service list + :rtype: schemas.ExtListComputeServicesResponse + """ + assert_system_admin_or_reader( + profile=profile, + exception="Not allowed to get compute services.", + ) + + system_session = utils.get_system_session() + + kwargs = {} + if binary is not None: + kwargs["binary"] = binary + if host is not None: + kwargs["host"] = host + services = await nova.list_services( + profile=profile, + session=system_session, + **kwargs, + ) + services = [Service(service).to_dict() for service in services] + return {"services": services} diff --git a/src/skyline_apiserver/api/v1/login.py b/src/skyline_apiserver/api/v1/login.py new file mode 100644 index 0000000..108201a --- /dev/null +++ b/src/skyline_apiserver/api/v1/login.py @@ -0,0 +1,190 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from fastapi import APIRouter, Depends, HTTPException, Request, Response, status +from keystoneauth1.identity.v3 import Password +from keystoneauth1.session import Session as osSession +from keystoneclient.client import Client as KeystoneClient +from skyline_log import LOG + +from skyline_apiserver import schemas +from skyline_apiserver.api import deps +from skyline_apiserver.client import utils +from skyline_apiserver.client.openstack.keystone import revoke_token +from skyline_apiserver.client.openstack.system import ( + get_endpoints, + get_project_scope_token, + get_projects, +) +from skyline_apiserver.client.utils import generate_session, get_system_session +from skyline_apiserver.config import CONF +from skyline_apiserver.core.security import ( + generate_profile, + generate_profile_by_token, + parse_access_token, +) +from skyline_apiserver.db import api as db_api + +router = APIRouter() + + +async def _patch_profile(profile) -> schemas.Profile: + try: + profile.endpoints = await get_endpoints(region=profile.region) + profile.projects = await get_projects(region=profile.region, user=profile.user.id) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + return profile + + +@router.post( + "/login", + description="Login & get user profile.", + responses={ + 200: {"model": schemas.Profile}, + 401: {"model": schemas.common.UnauthorizedMessage}, + }, + response_model=schemas.Profile, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def login(credential: schemas.Credential, response: Response): + try: + auth_url = await utils.get_endpoint( + region=credential.region, + service="keystone", + session=get_system_session(), + ) + unscope_auth = Password( + auth_url=auth_url, + user_domain_name=credential.domain, + username=credential.username, + password=credential.password, + reauthenticate=False, + ) + session = osSession(auth=unscope_auth, verify=False) + unscope_client = KeystoneClient(session=session, endpoint=auth_url) + project_scope = unscope_client.auth.projects() + # we must get the project_scope with enabled project + project_scope = [scope for scope in project_scope if scope.enabled] + if not project_scope: + raise Exception("You are not authorized for any projects or domains.") + + project_scope_token = await get_project_scope_token( + keystone_token=session.get_token(), + region=credential.region, + project_id=project_scope[0].id, + ) + + profile = await generate_profile( + keystone_token=project_scope_token, + region=credential.region, + ) + + profile = await _patch_profile(profile) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + else: + response.set_cookie(CONF.default.session_name, profile.toJWTPayload()) + return profile + + +@router.get( + "/profile", + description="Get user profile.", + responses={ + 200: {"model": schemas.Profile}, + 401: {"model": schemas.common.UnauthorizedMessage}, + }, + response_model=schemas.Profile, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def get_profile(profile: schemas.Profile = Depends(deps.get_profile_update_jwt)): + return await _patch_profile(profile) + + +@router.post( + "/logout", + description="Log out.", + responses={ + 200: {"model": schemas.common.OK}, + }, + response_model=schemas.common.OK, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def logout( + response: Response, + request: Request, + payload: str = Depends(deps.getJWTPayload), +): + if payload: + try: + token = parse_access_token(payload) + profile = await generate_profile_by_token(token) + session = await generate_session(profile) + await revoke_token(profile, session, token.keystone_token) + await db_api.revoke_token(profile.uuid, profile.exp) + except Exception as e: + LOG.debug(str(e)) + response.delete_cookie(CONF.default.session_name) + return schemas.common.OK(message="Logout OK") + + +@router.post( + "/switch_project/{project_id}", + description="Switch project.", + responses={ + 200: {"model": schemas.Profile}, + 401: {"model": schemas.common.UnauthorizedMessage}, + }, + response_model=schemas.Profile, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def switch_project( + project_id: str, + response: Response, + profile: schemas.Profile = Depends(deps.get_profile), +): + try: + project_scope_token = await get_project_scope_token( + keystone_token=profile.keystone_token, + region=profile.region, + project_id=project_id, + ) + + profile = await generate_profile( + keystone_token=project_scope_token, + region=profile.region, + uuid_value=profile.uuid, + ) + profile = await _patch_profile(profile) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + else: + response.set_cookie(CONF.default.session_name, profile.toJWTPayload()) + return profile diff --git a/src/skyline_apiserver/api/v1/openstack/__init__.py b/src/skyline_apiserver/api/v1/openstack/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/api/v1/openstack/base.py b/src/skyline_apiserver/api/v1/openstack/base.py new file mode 100644 index 0000000..da6d166 --- /dev/null +++ b/src/skyline_apiserver/api/v1/openstack/base.py @@ -0,0 +1,240 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import copy + + +class APIResourceWrapper(object): + """Simple wrapper for api objects. + + Define _attrs on the child class and pass in the + api object as the only argument to the constructor + """ + + _attrs = [] + _apiresource = None # Make sure _apiresource is there even in __init__. + + def __init__(self, apiresource): + self._apiresource = apiresource + + def __getattribute__(self, attr): + try: + return object.__getattribute__(self, attr) + except AttributeError: + if attr not in self._attrs: + raise + # __getattr__ won't find properties + return getattr(self._apiresource, attr) + + def __repr__(self): + return "<%s: %s>" % ( + self.__class__.__name__, + dict((attr, getattr(self, attr)) for attr in self._attrs if hasattr(self, attr)), + ) + + def to_dict(self): + obj = {} + for key in self._attrs: + obj[key] = getattr(self, key, None) + return obj + + @property + def name_or_id(self): + return self.name or "(%s)" % self.id[:13] + + +class APIDictWrapper(object): + """Simple wrapper for api dictionaries + + Some api calls return dictionaries. This class provides identical + behavior as APIResourceWrapper, except that it will also behave as a + dictionary, in addition to attribute accesses. + + Attribute access is the preferred method of access, to be + consistent with api resource objects from novaclient. + """ + + _apidict = {} # Make sure _apidict is there even in __init__. + + def __init__(self, apidict): + self._apidict = apidict + + def __getattribute__(self, attr): + try: + return object.__getattribute__(self, attr) + except AttributeError: + if attr not in self._apidict: + raise + return self._apidict[attr] + + def __getitem__(self, item): + try: + return getattr(self, item) + except (AttributeError, TypeError) as e: + # caller is expecting a KeyError + raise KeyError(e) + + def __contains__(self, item): + try: + return hasattr(self, item) + except TypeError: + return False + + def get(self, item, default=None): + try: + return getattr(self, item) + except (AttributeError, TypeError): + return default + + def __repr__(self): + return "<%s: %s>" % (self.__class__.__name__, self._apidict) + + def to_dict(self): + return self._apidict + + +class OSServer(APIResourceWrapper): + + _attrs = [ + "accessIPv4", + "accessIPv6", + "addresses", + "config_drive", + "created", + "flavor", + "hostId", + "id", + "image", + "key_name", + "links", + "metadata", + "name", + "OS-DCF:diskConfig", + "OS-EXT-AZ:availability_zone", + "OS-EXT-SRV-ATTR:host", + "OS-EXT-SRV-ATTR:hypervisor_hostname", + "OS-EXT-SRV-ATTR:instance_name", + "OS-EXT-STS:power_state", + "OS-EXT-STS:task_state", + "OS-EXT-STS:vm_state", + "os-extended-volumes:volumes_attached", + "OS-SRV-USG:launched_at", + "OS-SRV-USG:terminated_at", + "status", + "tenant_id", + "updated", + "user_id", + "fault", + "progress", + "security_groups", + "servers_links", + "OS-EXT-SRV-ATTR:hostname", + "OS-EXT-SRV-ATTR:reservation_id", + "OS-EXT-SRV-ATTR:launch_index", + "OS-EXT-SRV-ATTR:kernel_id", + "OS-EXT-SRV-ATTR:ramdisk_id", + "OS-EXT-SRV-ATTR:root_device_name", + "OS-EXT-SRV-ATTR:user_data", + "locked", + "host_status", + "description", + "tags", + "trusted_image_certificates", + "locked_reason", + ] + + +class OSVolume(APIResourceWrapper): + _attrs = [ + "migration_status", + "attachments", + "links", + "availability_zone", + "os-vol-host-attr:host", + "encrypted", + "encryption_key_id", + "updated_at", + "replication_status", + "snapshot_id", + "id", + "size", + "user_id", + "os-vol-tenant-attr:tenant_id", + "os-vol-mig-status-attr:migstat", + "metadata", + "status", + "volume_image_metadata", + "description", + "multiattach", + "source_volid", + "consistencygroup_id", + "os-vol-mig-status-attr:name_id", + "name", + "bootable", + "created_at", + "volume_type", + "volume_type_id", + "group_id", + "volumes_links", + ] + + +class OSVolumeSnapshot(APIResourceWrapper): + _attrs = [ + "status", + "os-extended-snapshot-attributes:progress", + "description", + "created_at", + "name", + "user_id", + "volume_id", + "volume_type_id", + "os-extended-snapshot-attributes:project_id", + "size", + "id", + "metadata", + "updated_at", + "snapshots_links", + ] + + +class NeutronAPIDictWrapper(APIDictWrapper): + def __init__(self, apidict): + if "admin_state_up" in apidict: + if apidict["admin_state_up"]: + apidict["admin_state"] = "UP" + else: + apidict["admin_state"] = "DOWN" + super(NeutronAPIDictWrapper, self).__init__(apidict) + + +class PortAllowedAddressPair(NeutronAPIDictWrapper): + """Wrapper for neutron port allowed address pairs.""" + + def __init__(self, addr_pair): + super(PortAllowedAddressPair, self).__init__(addr_pair) + + +class OSPort(NeutronAPIDictWrapper): + """Wrapper for neutron ports.""" + + def __init__(self, apidict): + pairs = apidict.get("allowed_address_pairs") + if pairs: + apidict = copy.deepcopy(apidict) + wrapped_pairs = [PortAllowedAddressPair(pair) for pair in pairs] + apidict["allowed_address_pairs"] = wrapped_pairs + super(OSPort, self).__init__(apidict) diff --git a/src/skyline_apiserver/api/v1/policy.py b/src/skyline_apiserver/api/v1/policy.py new file mode 100644 index 0000000..7504e48 --- /dev/null +++ b/src/skyline_apiserver/api/v1/policy.py @@ -0,0 +1,92 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from fastapi import APIRouter, Depends, HTTPException, status + +from skyline_apiserver import schemas +from skyline_apiserver.api import deps +from skyline_apiserver.client.utils import generate_session, get_access +from skyline_apiserver.policies import ENFORCER, UserContext +from skyline_apiserver.schemas import Policies, PoliciesRules, common + +router = APIRouter() + + +@router.get( + "/policies", + description="List policies and permissions", + responses={ + 200: {"model": Policies}, + 401: {"model": common.UnauthorizedMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=Policies, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_policies( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), +): + session = await generate_session(profile) + access = await get_access(session) + user_context = UserContext(access) + target = { + "user_id": profile.user.id, + "project_id": profile.project.id, + } + result = [ + {"rule": rule, "allowed": ENFORCER.authorize(rule, target, user_context)} + for rule in ENFORCER.rules + ] + return {"policies": result} + + +@router.post( + "/policies/check", + description="Check policies permissions", + responses={ + 200: {"model": Policies}, + 401: {"model": common.UnauthorizedMessage}, + 403: {"model": common.ForbiddenMessage}, + 500: {"model": common.InternalServerErrorMessage}, + }, + response_model=Policies, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def check_policies( + policy_rules: PoliciesRules, + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), +): + session = await generate_session(profile) + access = await get_access(session) + user_context = UserContext(access) + target = { + "user_id": profile.user.id, + "project_id": profile.project.id, + } + try: + result = [ + {"rule": rule, "allowed": ENFORCER.authorize(rule, target, user_context)} + for rule in policy_rules.rules + ] + except Exception as e: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=str(e), + ) + + return {"policies": result} diff --git a/src/skyline_apiserver/api/v1/setting.py b/src/skyline_apiserver/api/v1/setting.py new file mode 100644 index 0000000..e23d892 --- /dev/null +++ b/src/skyline_apiserver/api/v1/setting.py @@ -0,0 +1,140 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from fastapi import APIRouter, Depends, HTTPException, status + +from skyline_apiserver import schemas +from skyline_apiserver.api import deps +from skyline_apiserver.config import CONF +from skyline_apiserver.db import api as db_api +from skyline_apiserver.types import constants +from skyline_apiserver.utils.roles import assert_system_admin + +router = APIRouter() + + +def assert_setting_key_exist(key: str): + if key not in CONF.setting.base_settings: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Not a valid setting key.", + ) + + +@router.get( + "/setting/{key}", + description="Get a setting item.", + responses={ + 200: {"model": schemas.Setting}, + 401: {"model": schemas.common.UnauthorizedMessage}, + 404: {"model": schemas.common.NotFoundMessage}, + }, + response_model=schemas.Setting, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def show_setting( + key: str, + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), +) -> schemas.Setting: + assert_setting_key_exist(key) + setting = await db_api.get_setting(key) + value = getattr(CONF.setting, key) if setting is None else setting.value + hidden = key in constants.SETTINGS_HIDDEN_SET + restart_service = key in constants.SETTINGS_RESTART_SET + return schemas.Setting(key=key, value=value, hidden=hidden, restart_service=restart_service) + + +@router.put( + "/setting", + description="Update a setting item.", + responses={ + 200: {"model": schemas.Setting}, + 401: {"model": schemas.common.UnauthorizedMessage}, + 403: {"model": schemas.common.ForbiddenMessage}, + 404: {"model": schemas.common.NotFoundMessage}, + 422: {"model": schemas.common.UnprocessableEntityMessage}, + }, + response_model=schemas.Setting, + status_code=status.HTTP_200_OK, + response_description="ok", +) +async def update_setting( + setting: schemas.UpdateSetting, + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), +) -> schemas.Setting: + assert_system_admin(profile=profile, exception="Not allowed to update settings.") + assert_setting_key_exist(setting.key) + setting = await db_api.update_setting(setting.key, setting.value) + hidden = setting.key in constants.SETTINGS_HIDDEN_SET + restart_service = setting.key in constants.SETTINGS_RESTART_SET + return schemas.Setting( + key=setting.key, + value=setting.value, + hidden=hidden, + restart_service=restart_service, + ) + + +@router.get( + "/settings", + description="Get all settings.", + responses={ + 200: {"model": schemas.Settings}, + 401: {"model": schemas.common.UnauthorizedMessage}, + }, + response_model=schemas.Settings, + status_code=status.HTTP_200_OK, + response_description="OK", +) +async def list_settings( + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), +) -> schemas.Settings: + settings = { + k: schemas.Setting( + key=k, + value=getattr(CONF.setting, k), + hidden=k in constants.SETTINGS_HIDDEN_SET, + restart_service=k in constants.SETTINGS_RESTART_SET, + ) + for k in CONF.setting.base_settings + } + db_settings = await db_api.list_settings() + for item in db_settings: + settings[item.key].value = item.value + settings = list(settings.values()) + return schemas.Settings(settings=settings) + + +@router.delete( + "/setting/{key}", + description="Reset a setting item to default", + responses={ + 204: {"model": None}, + 401: {"model": schemas.common.UnauthorizedMessage}, + 403: {"model": schemas.common.ForbiddenMessage}, + 404: {"model": schemas.common.NotFoundMessage}, + }, + status_code=status.HTTP_204_NO_CONTENT, + response_description="No Content", +) +async def reset_setting( + key: str, + profile: schemas.Profile = Depends(deps.get_profile_update_jwt), +) -> None: + assert_system_admin(profile=profile, exception="Not allowed to reset settings.") + assert_setting_key_exist(key) + await db_api.delete_setting(key) diff --git a/src/skyline_apiserver/api/v1/utils.py b/src/skyline_apiserver/api/v1/utils.py new file mode 100644 index 0000000..52a970d --- /dev/null +++ b/src/skyline_apiserver/api/v1/utils.py @@ -0,0 +1,263 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict, List + + +class APIResourceWrapper(object): + """Simple wrapper for api objects.""" + + _attrs: List[str] = [] + _attrs_mapping: Dict[str, Any] = {} + _apiresource: Any = None + + def __init__(self, apiresource: Any) -> None: + self._apiresource = apiresource + + def __getattribute__(self, attr: str) -> Any: + try: + return object.__getattribute__(self, attr) + except AttributeError: + raise + + def __repr__(self) -> str: + return "<%s: %s>" % ( + self.__class__.__name__, + self.to_dict(), + ) + + def _get_value(self, key: str) -> Any: + if isinstance(self._apiresource, dict): + value = self._apiresource.get(key, None) + else: + value = getattr(self._apiresource, key, None) + return value + + def to_dict(self) -> Dict[str, Any]: + obj: Dict[str, Any] = {} + for key, value in self._attrs_mapping.items(): + obj[key] = self._get_value(value) + return obj + + +class Server(APIResourceWrapper): + + _attrs_mapping = { + "id": "id", + "name": "name", + "project_id": "tenant_id", + "project_name": "project_name", + "host": "OS-EXT-SRV-ATTR:host", + "hostname": "OS-EXT-SRV-ATTR:hostname", + "image": "image", + "image_name": "image_name", + "image_os_distro": "image_os_distro", + "fixed_addresses": "fixed_addresses", + "floating_addresses": "floating_addresses", + "flavor": "flavor", + "flavor_info": "flavor", + "status": "status", + "locked": "locked", + "created_at": "created", + "updated_at": "updated", + "task_state": "OS-EXT-STS:task_state", + "vm_state": "OS-EXT-STS:vm_state", + "power_state": "OS-EXT-STS:power_state", + "volumes_attached": "os-extended-volumes:volumes_attached", + "root_device_name": "OS-EXT-SRV-ATTR:root_device_name", + "metadata": "metadata", + } + + def _format_flavor(self, obj: Dict[str, Any], key: str, return_key: str) -> None: + flavor = self._get_value(key) + obj[return_key] = flavor["original_name"] if flavor else None + + def _format_addr( + self, + obj: Dict[str, Any], + key: str, + address_type: str, + return_key: str, + ) -> None: + addresses = [] + for _, v in self._get_value(key).items(): + addresses.extend(v) + _addresses = [] + for address in addresses: + if address.get("OS-EXT-IPS:type") == address_type: + _addresses.append(address.get("addr")) + obj[return_key] = _addresses + + def _format_image(self, obj: Dict[str, Any], key: str, return_key: str) -> None: + image = self._get_value(key) + obj[return_key] = image["id"] if image else None + + def to_dict(self) -> Dict[str, Any]: + obj: Dict[str, Any] = {} + for key, value in self._attrs_mapping.items(): + if key == "flavor": + self._format_flavor(obj, "flavor", key) + continue + if value == "fixed_addresses": + self._format_addr(obj, "addresses", "fixed", key) + continue + if value == "floating_addresses": + self._format_addr(obj, "addresses", "floating", key) + continue + if value == "image": + self._format_image(obj, "image", key) + continue + obj[key] = self._get_value(value) + return obj + + +class Volume(APIResourceWrapper): + + _attrs_mapping = { + "id": "id", + "name": "name", + "project_id": "os-vol-tenant-attr:tenant_id", + "project_name": "project_name", + "host": "os-vol-host-attr:host", + "snapshot_id": "snapshot_id", + "source_volid": "source_volid", + "size": "size", + "status": "status", + "volume_type": "volume_type", + "attachments": "attachments", + "encrypted": "encrypted", + "bootable": "bootable", + "multiattach": "multiattach", + "availability_zone": "availability_zone", + "created_at": "created_at", + "volume_image_metadata": "volume_image_metadata", + } + + +class VolumeSnapshot(APIResourceWrapper): + + _attrs_mapping = { + "id": "id", + "name": "name", + "project_id": "os-extended-snapshot-attributes:project_id", + "project_name": "project_name", + "size": "size", + "status": "status", + "volume_id": "volume_id", + "volume_name": "volume_name", + "created_at": "created_at", + "metadata": "metadata", + } + + +class Flavor(APIResourceWrapper): + + _attrs_mapping = { + "id": "id", + "name": "name", + "vcpus": "vcpus", + "ram": "ram", + "disk": "disk", + "is_public": "os-flavor-access:is_public", + "is_in_use": "is_in_use", + } + + +class Service(APIResourceWrapper): + + _attrs_mapping = { + "id": "id", + "binary": "binary", + "disabled_reason": "disabled_reason", + "host": "host", + "state": "state", + "status": "status", + "updated_at": "updated_at", + "forced_down": "forced_down", + "zone": "zone", + } + + +class Image(APIResourceWrapper): + _attrs_mapping = { + "id": "id", + "name": "name", + "os_distro": "os_distro", + "block_device_mapping": "block_device_mapping", + "image_type": "image_type", + "status": "status", + "visibility": "visibility", + "disk_format": "disk_format", + "size": "size", + "created_at": "created_at", + } + + +class Project(APIResourceWrapper): + _attrs_mapping = { + "id": "id", + "name": "name", + } + + +class Port(APIResourceWrapper): + _attrs_mapping = { + "id": "id", + "name": "name", + "ipv4": "fixed_ips", + "ipv6": "fixed_ips", + "mac_address": "mac_address", + "device_owner": "device_owner", + "device_id": "device_id", + "server_name": "server_name", + "status": "status", + "created_at": "created_at", + "project_id": "project_id", + "network_id": "network_id", + "binding_vnic_type": "binding:vnic_type", + "description": "description", + "port_security_enabled": "port_security_enabled", + "qos_policy_id": "qos_policy_id", + "fixed_ips": "fixed_ips", + } + + def _format_ip( + self, + obj: Dict[str, Any], + key: str, + return_key: str, + ) -> None: + ips = [] + fixed_ips = self._get_value(key) + if fixed_ips: + for ip in fixed_ips: + if return_key == "ipv4" and ":" not in ip["ip_address"]: + ips.append(ip["ip_address"]) + elif return_key == "ipv6" and "." not in ip["ip_address"]: + ips.append(ip["ip_address"]) + obj[return_key] = ips + + def to_dict(self) -> Dict[str, Any]: + obj: Dict[str, Any] = {} + for key, value in self._attrs_mapping.items(): + if key == "ipv4": + self._format_ip(obj, "fixed_ips", key) + continue + if key == "ipv6": + self._format_ip(obj, "fixed_ips", key) + continue + obj[key] = self._get_value(value) + return obj diff --git a/src/skyline_apiserver/client/__init__.py b/src/skyline_apiserver/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/client/openstack/__init__.py b/src/skyline_apiserver/client/openstack/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/client/openstack/cinder.py b/src/skyline_apiserver/client/openstack/cinder.py new file mode 100644 index 0000000..48e4294 --- /dev/null +++ b/src/skyline_apiserver/client/openstack/cinder.py @@ -0,0 +1,83 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict + +from fastapi import HTTPException, status +from keystoneauth1.exceptions.http import Unauthorized +from keystoneauth1.session import Session +from starlette.concurrency import run_in_threadpool + +from skyline_apiserver import schemas +from skyline_apiserver.client import utils + + +async def list_volumes( + profile: schemas.Profile, + session: Session, + limit: int = None, + marker: str = None, + search_opts: Dict[str, Any] = None, + sort: str = None, +) -> Any: + try: + cc = await utils.cinder_client(region=profile.region, session=session) + return await run_in_threadpool( + cc.volumes.list, + search_opts=search_opts, + limit=limit, + marker=marker, + sort=sort, + ) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +async def list_volume_snapshots( + profile: schemas.Profile, + session: Session, + limit: int = None, + marker: str = None, + search_opts: Dict[str, Any] = None, + sort: str = None, +) -> Any: + try: + cc = await utils.cinder_client(region=profile.region, session=session) + return await run_in_threadpool( + cc.volume_snapshots.list, + search_opts=search_opts, + limit=limit, + marker=marker, + sort=sort, + ) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) diff --git a/src/skyline_apiserver/client/openstack/glance.py b/src/skyline_apiserver/client/openstack/glance.py new file mode 100644 index 0000000..12be357 --- /dev/null +++ b/src/skyline_apiserver/client/openstack/glance.py @@ -0,0 +1,48 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict + +from fastapi import HTTPException, status +from keystoneauth1.exceptions.http import Unauthorized +from keystoneauth1.session import Session +from starlette.concurrency import run_in_threadpool + +from skyline_apiserver import schemas +from skyline_apiserver.client import utils + + +async def list_images( + profile: schemas.Profile, + session: Session, + filters: Dict[str, Any] = {}, +) -> Any: + try: + kwargs = {} + if filters: + kwargs["filters"] = filters + gc = await utils.glance_client(session=session, region=profile.region) + return await run_in_threadpool(gc.images.list, **kwargs) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) diff --git a/src/skyline_apiserver/client/openstack/keystone.py b/src/skyline_apiserver/client/openstack/keystone.py new file mode 100644 index 0000000..c93a5c9 --- /dev/null +++ b/src/skyline_apiserver/client/openstack/keystone.py @@ -0,0 +1,73 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict + +from fastapi import HTTPException, status +from keystoneauth1.exceptions.http import Unauthorized +from keystoneauth1.session import Session +from starlette.concurrency import run_in_threadpool + +from skyline_apiserver import schemas +from skyline_apiserver.client import utils + + +async def list_projects( + profile: schemas.Profile, + all_projects: bool, + session: Session, + search_opts: Dict[str, Any] = {}, +) -> Any: + try: + kc = await utils.keystone_client(session=session, region=profile.region) + if not all_projects: + search_opts["user"] = profile.user.id + return await run_in_threadpool(kc.projects.list, **search_opts) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +async def revoke_token( + profile: schemas.Profile, + session: Session, + token: str, +) -> None: + """Revoke a token. + :param token: The token to be revoked. + :type token: str or :class:`keystoneclient.access.AccessInfo` + """ + try: + kc = await utils.keystone_client(session=session, region=profile.region) + kwargs = {"token": token} + await run_in_threadpool(kc.tokens.revoke_token, **kwargs) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) diff --git a/src/skyline_apiserver/client/openstack/neutron.py b/src/skyline_apiserver/client/openstack/neutron.py new file mode 100644 index 0000000..351ed6f --- /dev/null +++ b/src/skyline_apiserver/client/openstack/neutron.py @@ -0,0 +1,41 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any + +from fastapi import HTTPException, status +from keystoneauth1.exceptions.http import Unauthorized +from keystoneauth1.session import Session +from starlette.concurrency import run_in_threadpool + +from skyline_apiserver import schemas +from skyline_apiserver.client import utils + + +async def list_networks(profile: schemas.Profile, session: Session, **kwargs: Any) -> Any: + try: + nc = await utils.neutron_client(session=session, region=profile.region) + return await run_in_threadpool(nc.list_networks, **kwargs) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) diff --git a/src/skyline_apiserver/client/openstack/nova.py b/src/skyline_apiserver/client/openstack/nova.py new file mode 100644 index 0000000..feb46f2 --- /dev/null +++ b/src/skyline_apiserver/client/openstack/nova.py @@ -0,0 +1,86 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict + +from fastapi import HTTPException, status +from keystoneauth1.exceptions.http import Unauthorized +from keystoneauth1.session import Session +from novaclient.exceptions import BadRequest, Forbidden +from starlette.concurrency import run_in_threadpool + +from skyline_apiserver import schemas +from skyline_apiserver.client import utils + + +async def list_servers( + profile: schemas.Profile, + session: Session, + search_opts: Dict[str, Any] = None, + marker: str = None, + limit: int = None, + sort_keys: str = None, + sort_dirs: str = None, +) -> Any: + try: + nc = await utils.nova_client( + region=profile.region, + session=session, + ) + return await run_in_threadpool( + nc.servers.list, + search_opts=search_opts, + marker=marker, + limit=limit, + sort_keys=sort_keys, + sort_dirs=sort_dirs, + ) + except BadRequest as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e), + ) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Forbidden as e: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +async def list_services(profile: schemas.Profile, session: Session, **kwargs: Any) -> Any: + try: + nc = await utils.nova_client(region=profile.region, session=session) + return await run_in_threadpool(nc.services.list, **kwargs) + except Unauthorized as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) diff --git a/src/skyline_apiserver/client/openstack/system.py b/src/skyline_apiserver/client/openstack/system.py new file mode 100644 index 0000000..6617e27 --- /dev/null +++ b/src/skyline_apiserver/client/openstack/system.py @@ -0,0 +1,92 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from pathlib import PurePath +from typing import Any, Dict + +from keystoneauth1.identity.v3 import Token +from keystoneauth1.session import Session +from skyline_log import LOG +from starlette.concurrency import run_in_threadpool + +from skyline_apiserver.client import utils +from skyline_apiserver.client.utils import get_system_session +from skyline_apiserver.config import CONF + + +async def get_project_scope_token( + keystone_token: str, + region: str, + project_id: str, +) -> str: + auth_url = await utils.get_endpoint( + region=region, + service="keystone", + session=get_system_session(), + ) + kwargs = {"project_id": project_id} + scope_auth = Token(auth_url=auth_url, token=keystone_token, **kwargs) + + session = Session(auth=scope_auth, verify=False) + keystone_token = session.get_token() + + return keystone_token + + +async def get_endpoints(region: str) -> Dict[str, Any]: + access = await utils.get_access(session=get_system_session()) + catalogs = access.service_catalog.get_endpoints( + region_name=region, + interface=CONF.openstack.interface_type, + ) + endpoints = {} + for service_type, endpoint in catalogs.items(): + service = CONF.openstack.service_mapping.get(service_type) + if service is None: + continue + + path = PurePath("/").joinpath(CONF.openstack.nginx_prefix, region.lower(), service) + endpoints[service] = str(path) + nc = await utils.neutron_client(session=get_system_session(), region=region) + neutron_extentions = await run_in_threadpool(nc.list_extensions) + extentions_set = {i["alias"] for i in neutron_extentions["extensions"]} + for alias, mapping_name in CONF.openstack.extension_mapping.items(): + if alias in extentions_set: + endpoints[mapping_name] = endpoints["neutron"] + else: + LOG.info(f"The {alias} resource could not be found.") + return endpoints + + +async def get_projects(region: str, user: str) -> Dict[str, Any]: + kc = await utils.keystone_client(session=get_system_session(), region=region) + projects = { + i.id: {"name": i.name, "domain_id": i.domain_id} for i in kc.projects.list(user=user) + } + return projects + + +async def get_domains(region: str) -> Any: + kc = await utils.keystone_client(session=get_system_session(), region=region) + domains = [i.name for i in kc.domains.list(enabled=True)] + return domains + + +async def get_regions() -> Any: + access = await utils.get_access(session=get_system_session()) + catalogs = access.service_catalog.get_endpoints(interface=CONF.openstack.interface_type) + regions = list(set(j["region_id"] for i in catalogs for j in catalogs[i])) + return regions diff --git a/src/skyline_apiserver/client/utils.py b/src/skyline_apiserver/client/utils.py new file mode 100644 index 0000000..61870ec --- /dev/null +++ b/src/skyline_apiserver/client/utils.py @@ -0,0 +1,151 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any + +from cinderclient.client import Client as CinderClient +from glanceclient.client import Client as GlanceClient +from keystoneauth1.access.access import AccessInfoV3 +from keystoneauth1.identity.v3 import Password, Token +from keystoneauth1.session import Session +from keystoneclient.client import Client as KeystoneClient +from keystoneclient.httpclient import HTTPClient +from neutronclient.v2_0.client import Client as NeutronClient +from novaclient.client import Client as NovaClient +from osc_placement.http import SessionClient as PlacementClient +from starlette.concurrency import run_in_threadpool + +from skyline_apiserver import schemas +from skyline_apiserver.config import CONF +from skyline_apiserver.types import constants + +SESSION = None + + +async def generate_session(profile: schemas.Profile) -> Any: + auth_url = await get_endpoint( + region=profile.region, + service="keystone", + session=get_system_session(), + ) + kwargs = { + "auth_url": auth_url, + "token": profile.keystone_token, + "project_id": profile.project.id, + } + auth = Token(**kwargs) + session = Session(auth=auth, verify=False) + session.auth.auth_ref = await run_in_threadpool(session.auth.get_auth_ref, session) + return session + + +def get_system_session() -> Session: + global SESSION + if SESSION is not None: + return SESSION + + auth = Password( + auth_url=CONF.openstack.keystone_url, + user_domain_name=CONF.openstack.system_user_domain, + username=CONF.openstack.system_user_name, + password=CONF.openstack.system_user_password, + project_name=CONF.openstack.system_project, + project_domain_name=CONF.openstack.system_project_domain, + reauthenticate=True, + ) + SESSION = Session(auth=auth, verify=False) + return SESSION + + +async def get_access(session: Session) -> AccessInfoV3: + auth = session.auth + if auth._needs_reauthenticate(): + auth.auth_ref = await run_in_threadpool(auth.get_auth_ref, session) + return auth.auth_ref + + +async def get_endpoint(region: str, service: str, session: Session) -> Any: + access = await get_access(session=session) + service_catalog = access.service_catalog + endpoint = service_catalog.get_urls( + region_name=region, + service_name=service, + interface=CONF.openstack.interface_type, + ) + if not endpoint: + raise ValueError("Endpoint not found") + return endpoint[0] + + +async def keystone_client( + session: Session, + region: str, + version: str = constants.KEYSTONE_API_VERSION, +) -> HTTPClient: + endpoint = await get_endpoint(region, "keystone", session=session) + client = KeystoneClient(version=version, session=session, endpoint=endpoint) + return client + + +async def glance_client( + session: Session, + region: str, + version: str = constants.GLANCE_API_VERSION, +) -> HTTPClient: + endpoint = await get_endpoint(region, "glance", session=session) + client = GlanceClient(version=version, session=session, endpoint=endpoint) + return client + + +async def nova_client( + session: Session, + region: str, + version: str = constants.NOVA_API_VERSION, +) -> HTTPClient: + endpoint = await get_endpoint(region, "nova", session=session) + client = NovaClient(version=version, session=session, endpoint_override=endpoint) + return client + + +async def cinder_client( + session: Session, + region: str, + version: str = constants.CINDER_API_VERSION, +) -> HTTPClient: + endpoint = await get_endpoint(region, "cinderv3", session=session) + client = CinderClient(version=version, session=session, endpoint_override=endpoint) + return client + + +async def neutron_client( + session: Session, + region: str, + version: str = constants.NEUTRON_API_VERSION, +) -> HTTPClient: + endpoint = await get_endpoint(region, "neutron", session=session) + client = NeutronClient(version=version, session=session, endpoint_override=endpoint) + return client + + +async def placement_client( + session: Session, + region: str, + version: str = constants.PLACEMENT_API_VERSION, +) -> HTTPClient: + endpoint = await get_endpoint(region, "placement", session=session) + ks_filter = {"service_type": "placement", "endpoint_override": endpoint} + client = PlacementClient(api_version=version, session=session, ks_filter=ks_filter) + return client diff --git a/src/skyline_apiserver/cmd/__init__.py b/src/skyline_apiserver/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/cmd/generate_swagger.py b/src/skyline_apiserver/cmd/generate_swagger.py new file mode 100644 index 0000000..5e0f22b --- /dev/null +++ b/src/skyline_apiserver/cmd/generate_swagger.py @@ -0,0 +1,61 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import json +import sys +from logging import StreamHandler + +import aiofiles +import click +from skyline_log import LOG, setup as log_setup + +from skyline_apiserver.config import configure +from skyline_apiserver.main import app +from skyline_apiserver.utils.coroutine import sync_run + + +class CommandException(Exception): + EXIT_CODE = 1 + + +@click.command(help="Generate swagger file.") +@click.option( + "-o", + "--output-file", + "output_file_path", + default="swagger.json", + help=( + "The path of the output file, this file is used to generate a OpenAPI file for " + "use in the development process. (Default value: swagger.json)" + ), +) +@sync_run +async def main(output_file_path: str) -> None: + try: + configure("skyline-apiserver") + log_setup(StreamHandler()) + + swagger_dict = app.openapi() + async with aiofiles.open(output_file_path, mode="w") as f: + await f.write(json.dumps(swagger_dict, indent=4)) + + except CommandException as e: + LOG.error(e) + sys.exit(e.EXIT_CODE) + + +if __name__ == "__main__": + main() diff --git a/src/skyline_apiserver/config/__init__.py b/src/skyline_apiserver/config/__init__.py new file mode 100644 index 0000000..5579d86 --- /dev/null +++ b/src/skyline_apiserver/config/__init__.py @@ -0,0 +1,38 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import os + +from . import default, developer, openstack, setting +from .base import Configuration, Group + +CONF = Configuration() + + +def configure(project: str, setup: bool = True) -> None: + conf_modules = ( + (default.GROUP_NAME, default.ALL_OPTS), + (developer.GROUP_NAME, developer.ALL_OPTS), + (openstack.GROUP_NAME, openstack.ALL_OPTS), + (setting.GROUP_NAME, setting.ALL_OPTS), + ) + groups = [Group(*item) for item in conf_modules] + CONF(groups) + if setup: + CONF.setup(project, os.environ.copy()) + + +__all__ = ("CONF", "configure") diff --git a/src/skyline_apiserver/config/base.py b/src/skyline_apiserver/config/base.py new file mode 100644 index 0000000..45e2f41 --- /dev/null +++ b/src/skyline_apiserver/config/base.py @@ -0,0 +1,152 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import os +import warnings +from dataclasses import InitVar, dataclass, field +from pathlib import Path, PurePath +from typing import Any, Dict, Iterator, Sequence, Tuple, Type + +import yaml +from immutables import Map +from pydantic import BaseModel, create_model + +from skyline_apiserver import __version__ + + +@dataclass(frozen=True) +class Opt: + name: str + description: str + schema: Any + default: Any = None + deprecated: bool = False + value: Any = field(init=False, default=None) + _schema_model: Type[BaseModel] = field(init=False, repr=False) + + def __post_init__(self) -> None: + object.__setattr__( + self, + "_schema_model", + create_model(f"Opt(name='{self.name}')", value=(self.schema, ...)), + ) + + def load(self, value: Any) -> None: + value = self.default if value is None else value + self._schema_model(value=value) + object.__setattr__(self, "value", value) + if self.deprecated: + warnings.warn( + f"The config opt {self.name} is deprecated, will be deleted after the" + f" {__version__} version", + DeprecationWarning, + ) + + +@dataclass(repr=False, frozen=True) +class Group: + name: str + init_opts: InitVar[Sequence[Opt]] = tuple() + _opts: Map[str, Opt] = field(init=False, repr=False) + + def __post_init__(self, init_opts: Sequence[Opt]) -> None: + object.__setattr__(self, "_opts", Map({opt.name: opt for opt in init_opts})) + + def __getattr__(self, name: str) -> Any: + if name in self._opts: + return self._opts[name].value + raise AttributeError(name) + + def __contains__(self, key: Any) -> bool: + return self._opts.__contains__(key) + + def __iter__(self) -> Iterator[Any]: + return self._opts.__iter__() + + def __len__(self) -> int: + return self._opts.__len__() + + def __repr__(self) -> str: + items = ", ".join((f"{opt}=Opt(name='{opt}')" for opt in self._opts)) + return f"Group({items})" + + +@dataclass(repr=False, frozen=True) +class Configuration: + init_groups: InitVar[Sequence[Group]] = tuple() + config: Dict[str, Any] = field(init=False, default_factory=dict, repr=False) + _groups: Map[str, Group] = field(init=False, repr=False) + + def __post_init__(self, init_groups: Sequence[Group]) -> None: + object.__setattr__(self, "_groups", Map({group.name: group for group in init_groups})) + + @staticmethod + def _get_config_file(project: str, env: Dict[str, str]) -> Tuple[str, str]: + config_dir = env.get("OS_CONFIG_DIR", PurePath("/etc", project).as_posix()).strip() + + config_file = env.get( + "OS_CONFIG_FILE", + os.path.join(config_dir, f"{project}.yaml"), + ).strip() + + return (config_dir, config_file) + + def setup(self, project: str, env: Dict[str, str]) -> None: + config_dir, config_file = self._get_config_file(project, env) + if not Path(config_file).exists(): + raise ValueError("Not found config file: %s" % config_file) + + with open(config_file) as f: + try: + object.__setattr__(self, "config", yaml.safe_load(f)) + except Exception: + raise ValueError("Load config file error") + + for group in self._groups.values(): + for opt in group._opts.values(): + value = self.config.get(group.name, {}).get(opt.name) + opt.load(value) + + def cleanup(self) -> None: + for group in self._groups.values(): + for opt in group._opts.values(): + object.__setattr__(opt, "value", None) + object.__setattr__(self, "_groups", Map()) + object.__setattr__(self, "config", {}) + + def __getattr__(self, name: str) -> Group: + if name in self._groups: + return self._groups[name] + raise AttributeError(name) + + def __call__(self, init_groups: Sequence[Group]) -> Any: + object.__setattr__(self, "_groups", Map({group.name: group for group in init_groups})) + + def __contains__(self, key: Any) -> bool: + return self._groups.__contains__(key) + + def __iter__(self) -> Iterator[Any]: + return self._groups.__iter__() + + def __len__(self) -> int: + return self._groups.__len__() + + def __repr__(self) -> str: + items = ", ".join((f"{group}=Group(name='{group}')" for group in self._groups)) + return f"Configuration({items})" + + +__all__ = ("Opt", "Group", "Configuration") diff --git a/src/skyline_apiserver/config/default.py b/src/skyline_apiserver/config/default.py new file mode 100644 index 0000000..396b610 --- /dev/null +++ b/src/skyline_apiserver/config/default.py @@ -0,0 +1,91 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import List + +from pydantic import StrictBool, StrictInt, StrictStr + +from .base import Opt + +debug = Opt( + name="debug", + description="Enable debug", + schema=StrictBool, + default=False, +) + +log_dir = Opt( + name="log_dir", + description="Log directory", + schema=StrictStr, + default="./log", +) + +secret_key = Opt( + name="secret_key", + description="Secret key", + schema=StrictStr, + default="aCtmgbcUqYUy_HNVg5BDXCaeJgJQzHJXwqbXr0Nmb2o", +) + +access_token_expire = Opt( + name="access_token_expire", + description="Access token expire seconds", + schema=StrictInt, + default=60 * 60, +) + +access_token_renew = Opt( + name="access_token_renew", + description="access token renew seconds", + schema=StrictInt, + default=30 * 60, +) + +cors_allow_origins = Opt( + name="cors_allow_origins", + description="CORS allow origins", + schema=List[StrictStr], + default=[], +) + +session_name = Opt( + name="session_name", + description="Session name", + schema=StrictStr, + default="session", +) + +database_url = Opt( + name="database_url", + description="Database url", + schema=StrictStr, + default="mysql://root:root@localhost:3306/skyline", +) + +GROUP_NAME = __name__.split(".")[-1] +ALL_OPTS = ( + debug, + log_dir, + secret_key, + access_token_expire, + access_token_renew, + cors_allow_origins, + session_name, + database_url, +) + +__all__ = ("GROUP_NAME", "ALL_OPTS") diff --git a/src/skyline_apiserver/config/developer.py b/src/skyline_apiserver/config/developer.py new file mode 100644 index 0000000..86b00ad --- /dev/null +++ b/src/skyline_apiserver/config/developer.py @@ -0,0 +1,31 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from pydantic import StrictBool + +from .base import Opt + +show_raw_sql = Opt( + name="show_raw_sql", + description="Show raw sql", + schema=StrictBool, + default=False, +) + +GROUP_NAME = __name__.split(".")[-1] +ALL_OPTS = (show_raw_sql,) + +__all__ = ("GROUP_NAME", "ALL_OPTS") diff --git a/src/skyline_apiserver/config/openstack.py b/src/skyline_apiserver/config/openstack.py new file mode 100644 index 0000000..b1c33c9 --- /dev/null +++ b/src/skyline_apiserver/config/openstack.py @@ -0,0 +1,218 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Dict, List + +from pydantic import HttpUrl, StrictInt, StrictStr + +from skyline_apiserver.types import InterfaceType + +from .base import Opt + +keystone_url = Opt( + name="keystone_url", + description="Keystone endpoint address", + schema=HttpUrl, + default="http://localhost:5000/v3/", +) + +system_project_domain = Opt( + name="system_project_domain", + description="Skyline system project's domain", + schema=StrictStr, + default="Default", +) + +system_project = Opt( + name="system_project", + description="Skyline system project", + schema=StrictStr, + default="service", +) + +system_user_domain = Opt( + name="system_user_domain", + description="Skyline system user's domain", + schema=StrictStr, + default="Default", +) + +system_user_name = Opt( + name="system_user_name", + description="Skyline system username", + schema=StrictStr, + default="skyline", +) + +system_user_password = Opt( + name="system_user_password", + description="Skyline system 's password", + schema=StrictStr, + default="password", +) + +default_region = Opt( + name="default_region", + description="Skyline default region", + schema=StrictStr, + default="RegionOne", +) + +interface_type = Opt( + name="interface_type", + description="OpenStack endpoint interface type", + schema=InterfaceType, + default="internal", +) + +nginx_prefix = Opt( + name="nginx_prefix", + description="Endpoint prefix", + schema=StrictStr, + default="/api/openstack", +) + +base_roles = Opt( + name="base_roles", + description="base roles list", + schema=List[StrictStr], + default=[ + "keystone_system_admin", + "keystone_system_reader", + "keystone_project_admin", + "keystone_project_member", + "keystone_project_reader", + "nova_system_admin", + "nova_system_reader", + "nova_project_admin", + "nova_project_member", + "nova_project_reader", + "cinder_system_admin", + "cinder_system_reader", + "cinder_project_admin", + "cinder_project_member", + "cinder_project_reader", + "glance_system_admin", + "glance_system_reader", + "glance_project_admin", + "glance_project_member", + "glance_project_reader", + "neutron_system_admin", + "neutron_system_reader", + "neutron_project_admin", + "neutron_project_member", + "neutron_project_reader", + "heat_system_admin", + "heat_system_reader", + "heat_project_admin", + "heat_project_member", + "heat_project_reader", + "placement_system_admin", + "placement_system_reader", + "panko_system_admin", + "panko_system_reader", + "panko_project_admin", + "panko_project_member", + "panko_project_reader", + "ironic_system_admin", + "ironic_system_reader", + "octavia_system_admin", + "octavia_system_reader", + "octavia_project_admin", + "octavia_project_member", + "octavia_project_reader", + ], +) + +base_domains = Opt( + name="base_domains", + description="base domains list", + schema=List[StrictStr], + default=[ + "heat_user_domain", + ], +) + +system_admin_roles = Opt( + name="system_admin_roles", + description="system admin roles have system permission", + schema=List[StrictStr], + default=["admin", "system_admin"], +) + +system_reader_roles = Opt( + name="system_reader_roles", + description="system reader roles have system permission", + schema=List[StrictStr], + default=["system_reader"], +) + +service_mapping = Opt( + name="service_mapping", + description=( + "openstack service mapping, service mapping in the format :" + ), + schema=Dict[StrictStr, StrictStr], + default={ + "volumev3": "cinder", + "image": "glance", + "orchestration": "heat", + "identity": "keystone", + "network": "neutron", + "compute": "nova", + "placement": "placement", + }, +) + +extension_mapping = Opt( + name="extension_mapping", + description="Mapping of extension from extensions api", + schema=Dict[StrictStr, StrictStr], + default={ + "vpnaas": "neutron_vpn", + "fwaas_v2": "neutron_firewall", + }, +) + +reclaim_instance_interval = Opt( + name="reclaim_instance_interval", + description="reclaim instance interval", + schema=StrictInt, + default=60 * 60 * 24 * 7, +) + + +GROUP_NAME = __name__.split(".")[-1] +ALL_OPTS = ( + keystone_url, + system_project_domain, + system_project, + system_user_domain, + system_user_name, + system_user_password, + default_region, + interface_type, + nginx_prefix, + base_roles, + base_domains, + system_admin_roles, + system_reader_roles, + service_mapping, + extension_mapping, + reclaim_instance_interval, +) + +__all__ = ("GROUP_NAME", "ALL_OPTS") diff --git a/src/skyline_apiserver/config/setting.py b/src/skyline_apiserver/config/setting.py new file mode 100644 index 0000000..6a3f0f3 --- /dev/null +++ b/src/skyline_apiserver/config/setting.py @@ -0,0 +1,113 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict, List + +from pydantic.types import StrictStr + +from .base import Opt + +base_settings = Opt( + name="base_settings", + description="base settings list", + schema=List[StrictStr], + default=[ + "flavor_families", + "gpu_models", + "usb_models", + "license", + "currency", + ], +) + +flavor_families = Opt( + name="flavor_families", + description="Flavor families", + schema=List[Dict[str, Any]], + default=[ + { + "architecture": "x86_architecture", + "categories": [ + {"name": "general_purpose", "properties": []}, + {"name": "compute_optimized", "properties": []}, + {"name": "memory_optimized", "properties": []}, + # {"name": "big_data", "properties": []}, + # {"name": "local_ssd", "properties": []}, + {"name": "high_clock_speed", "properties": []}, + ], + }, + { + "architecture": "heterogeneous_computing", + "categories": [ + {"name": "compute_optimized_type_with_gpu", "properties": []}, + {"name": "visualization_compute_optimized_type_with_gpu", "properties": []}, + # {"name": "compute_optimized_type", "properties": []}, + ], + }, + # { + # "architecture": "arm_architecture", + # "categories": [ + # {"name": "general_purpose", "properties": []}, + # {"name": "compute_optimized", "properties": []}, + # {"name": "memory_optimized", "properties": []}, + # {"name": "big_data", "properties": []}, + # {"name": "local_ssd", "properties": []}, + # {"name": "high_clock_speed", "properties": []}, + # ], + # }, + ], +) + +gpu_models = Opt( + name="gpu_models", + description="gpu_models", + schema=List[StrictStr], + default=["nvidia_t4"], +) + +usb_models = Opt( + name="usb_models", + description="usb_models", + schema=List[StrictStr], + default=["usb_c"], +) + +license_info = Opt( + name="license", + description="license", + schema=StrictStr, + default="", +) + +currency_info = Opt( + name="currency", + description="currency", + schema=Dict[StrictStr, StrictStr], + default={"zh": "元", "en": "CNY"}, +) + + +GROUP_NAME = __name__.split(".")[-1] +ALL_OPTS = ( + base_settings, + flavor_families, + gpu_models, + usb_models, + license_info, + currency_info, +) + +__all__ = ("GROUP_NAME", "ALL_OPTS") diff --git a/src/skyline_apiserver/core/__init__.py b/src/skyline_apiserver/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/core/security.py b/src/skyline_apiserver/core/security.py new file mode 100644 index 0000000..d2372c2 --- /dev/null +++ b/src/skyline_apiserver/core/security.py @@ -0,0 +1,142 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import base64 +import json +import time +import uuid +import zlib +from typing import Optional + +from fastapi import HTTPException, status +from jose import jwt +from skyline_log import LOG + +from skyline_apiserver import schemas +from skyline_apiserver.client import utils +from skyline_apiserver.client.utils import get_system_session +from skyline_apiserver.config import CONF +from skyline_apiserver.db import api as db_api +from skyline_apiserver.types import constants + +LICENSE = None +CURRENCY = None + + +def parse_access_token(token: str) -> (schemas.Payload): + payload = jwt.decode(token, CONF.default.secret_key) + return schemas.Payload( + keystone_token=payload["keystone_token"], + region=payload["region"], + exp=payload["exp"], + uuid=payload["uuid"], + ) + + +async def generate_profile_by_token(token: schemas.Payload) -> schemas.Profile: + return await generate_profile( + keystone_token=token.keystone_token, + region=token.region, + exp=token.exp, + uuid_value=token.uuid, + ) + + +async def get_license() -> Optional[schemas.License]: + global LICENSE + if LICENSE is not None: + # Restart process or docker container to refresh + return LICENSE + + db_license = await db_api.get_setting("license") + if db_license is None: + return None + + raw_data_bs = db_license.value.encode("utf-8") + try: + license_bs = base64.decodebytes(raw_data_bs)[256:] + license_content = json.loads(zlib.decompress(license_bs)) + except Exception as e: + LOG.error(e) + msg = "License can not be parsed" + LOG.error(msg) + return None + + features = license_content["features"] + addons = any([features, lambda x: "addons" in x]) + # In order to compatible the old license[no include addons field], + # by default, we set firewall and loadbalance as default features. + if not addons: + features.append({"addons": ";".join(constants.ADDONS_DEFAULT)}) + + LICENSE = schemas.License( + name=license_content["name"], + summary=license_content["summary"], + macs=license_content["macs"], + features=features, + start=license_content["period"]["start"], + end=license_content["period"]["end"], + ) + + return LICENSE + + +async def get_currency() -> dict: + global CURRENCY + if CURRENCY is not None: + return CURRENCY + + db_currency = await db_api.get_setting("currency") + if db_currency and type(db_currency.value) == dict: + CURRENCY = db_currency.value + + if CURRENCY is None: + CURRENCY = getattr(CONF.setting, "currency") + return CURRENCY + + +async def generate_profile( + keystone_token: str, + region: str, + exp: int = None, + uuid_value: str = None, +) -> schemas.Profile: + license = await get_license() + currency = await get_currency() + try: + kc = await utils.keystone_client(session=get_system_session(), region=region) + token_data = kc.tokens.get_token_data(token=keystone_token) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e), + ) + else: + return schemas.Profile( + keystone_token=keystone_token, + region=region, + project=token_data["token"]["project"], + user=token_data["token"]["user"], + roles=token_data["token"]["roles"], + keystone_token_exp=token_data["token"]["expires_at"], + base_roles=CONF.openstack.base_roles, + base_domains=CONF.openstack.base_domains, + exp=exp or int(time.time()) + CONF.default.access_token_expire, + uuid=uuid_value or uuid.uuid4().hex, + version=constants.VERSION, + license=license, + currency=currency, + ) diff --git a/src/skyline_apiserver/db/__init__.py b/src/skyline_apiserver/db/__init__.py new file mode 100644 index 0000000..d700975 --- /dev/null +++ b/src/skyline_apiserver/db/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import api +from .base import setup + +__all__ = ("setup", "api") diff --git a/src/skyline_apiserver/db/alembic/README b/src/skyline_apiserver/db/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/src/skyline_apiserver/db/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/src/skyline_apiserver/db/alembic/__init__.py b/src/skyline_apiserver/db/alembic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/db/alembic/env.py b/src/skyline_apiserver/db/alembic/env.py new file mode 100644 index 0000000..bb08871 --- /dev/null +++ b/src/skyline_apiserver/db/alembic/env.py @@ -0,0 +1,81 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from logging import StreamHandler, basicConfig + +from alembic import context +from databases import DatabaseURL +from skyline_log import setup as log_setup +from sqlalchemy import create_engine, pool + +from skyline_apiserver.config import CONF, configure +from skyline_apiserver.db.models import METADATA + +configure("skyline-apiserver") +basicConfig() +log_setup(StreamHandler()) + +config = context.config +config.set_main_option("sqlalchemy.url", CONF.default.database_url) + +db_url = DatabaseURL(CONF.default.database_url) +if db_url.scheme == "mysql": + db_url = db_url.replace(dialect="mysql+pymysql") + +target_metadata = METADATA + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + context.configure( + url=str(db_url), + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + engine = create_engine(str(db_url), poolclass=pool.NullPool) + + with engine.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/src/skyline_apiserver/db/alembic/script.py.mako b/src/skyline_apiserver/db/alembic/script.py.mako new file mode 100644 index 0000000..55df286 --- /dev/null +++ b/src/skyline_apiserver/db/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/src/skyline_apiserver/db/alembic/versions/000_init.py b/src/skyline_apiserver/db/alembic/versions/000_init.py new file mode 100644 index 0000000..e454395 --- /dev/null +++ b/src/skyline_apiserver/db/alembic/versions/000_init.py @@ -0,0 +1,55 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""init + +Revision ID: 000 +Revises: +Create Date: 2020-11-22 06:43:23.717511 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "000" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "revoked_token", + sa.Column("uuid", sa.String(length=128), nullable=False), + sa.Column("expire", sa.Integer(), nullable=False), + ) + op.create_index(op.f("ix_revoked_token_uuid"), "revoked_token", ["uuid"], unique=False) + op.create_table( + "settings", + sa.Column("key", sa.String(length=128), nullable=False), + sa.Column("value", sa.JSON(), nullable=True), + ) + op.create_index(op.f("ix_settings_key"), "settings", ["key"], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_settings_key"), table_name="settings") + op.drop_table("settings") + op.drop_index(op.f("ix_revoked_token_uuid"), table_name="revoked_token") + op.drop_table("revoked_token") + # ### end Alembic commands ### diff --git a/src/skyline_apiserver/db/alembic/versions/__init__.py b/src/skyline_apiserver/db/alembic/versions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/db/api.py b/src/skyline_apiserver/db/api.py new file mode 100644 index 0000000..a345179 --- /dev/null +++ b/src/skyline_apiserver/db/api.py @@ -0,0 +1,123 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import time +from functools import wraps +from typing import Any + +from sqlalchemy import delete, func, insert, select, update + +from skyline_apiserver.types import Fn + +from .base import DB, inject_db +from .models import RevokedToken, Settings + + +def check_db_connected(fn: Fn) -> Fn: + @wraps(fn) + async def wrapper(*args: Any, **kwargs: Any) -> Any: + await inject_db() + db = DB.get() + assert db is not None, "Database is not connected." + return await fn(*args, **kwargs) + + return wrapper + + +@check_db_connected +async def check_token(token_id: str) -> bool: + count_label = "revoked_count" + query = ( + select([func.count(RevokedToken.c.uuid).label(count_label)]) + .select_from(RevokedToken) + .where(RevokedToken.c.uuid == token_id) + ) + db = DB.get() + async with db.transaction(): + result = await db.fetch_one(query) + + count = getattr(result, count_label, 0) + return count > 0 + + +@check_db_connected +async def revoke_token(token_id: str, expire: int) -> Any: + query = insert(RevokedToken) + db = DB.get() + async with db.transaction(): + result = await db.execute(query, {"uuid": token_id, "expire": expire}) + + return result + + +@check_db_connected +async def purge_revoked_token() -> Any: + now = int(time.time()) - 1 + query = delete(RevokedToken).where(RevokedToken.c.expire < now) + db = DB.get() + async with db.transaction(): + result = await db.execute(query) + + return result + + +@check_db_connected +async def list_settings() -> Any: + query = select([Settings]) + db = DB.get() + async with db.transaction(): + result = await db.fetch_all(query) + + return result + + +@check_db_connected +async def get_setting(key: str) -> Any: + query = select([Settings]).where(Settings.c.key == key) + db = DB.get() + async with db.transaction(): + result = await db.fetch_one(query) + + return result + + +@check_db_connected +async def update_setting(key: str, value: Any) -> Any: + get_query = ( + select([Settings.c.key, Settings.c.value]).where(Settings.c.key == key).with_for_update() + ) + db = DB.get() + async with db.transaction(): + is_exist = await db.fetch_one(get_query) + if is_exist is None: + query = insert(Settings) + await db.execute(query, {"key": key, "value": value}) + else: + query = update(Settings).where(Settings.c.key == key) + await db.execute(query, {"value": value}) + result = await db.fetch_one(get_query) + + return result + + +@check_db_connected +async def delete_setting(key: str) -> Any: + query = delete(Settings).where(Settings.c.key == key) + db = DB.get() + async with db.transaction(): + result = await db.execute(query) + + return result diff --git a/src/skyline_apiserver/db/base.py b/src/skyline_apiserver/db/base.py new file mode 100644 index 0000000..9fad2ed --- /dev/null +++ b/src/skyline_apiserver/db/base.py @@ -0,0 +1,53 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from contextvars import ContextVar + +from databases import Database, DatabaseURL, core + +from skyline_apiserver.config import CONF + +DATABASE = None +DB = ContextVar("skyline_db", default=None) + + +class ParallelDatabase(Database): + def connection(self) -> core.Connection: + return core.Connection(self._backend) + + +async def setup(): + db_url = DatabaseURL(CONF.default.database_url) + global DATABASE + if db_url.scheme == "mysql": + DATABASE = ParallelDatabase( + db_url, + minsize=1, + maxsize=100, + echo=CONF.default.debug, + charset="utf8", + client_flag=0, + ) + elif db_url.scheme == "sqlite": + DATABASE = ParallelDatabase(db_url) + else: + raise ValueError("Unsupported database backend") + await DATABASE.connect() + + +async def inject_db(): + global DATABASE + DB.set(DATABASE) diff --git a/src/skyline_apiserver/db/models.py b/src/skyline_apiserver/db/models.py new file mode 100644 index 0000000..f8976ef --- /dev/null +++ b/src/skyline_apiserver/db/models.py @@ -0,0 +1,34 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from sqlalchemy import JSON, Column, Integer, MetaData, String, Table + +METADATA = MetaData() + + +RevokedToken = Table( + "revoked_token", + METADATA, + Column("uuid", String(length=128), nullable=False, index=True, unique=False), + Column("expire", Integer, nullable=False), +) + +Settings = Table( + "settings", + METADATA, + Column("key", String(length=128), nullable=False, index=True, unique=True), + Column("value", JSON, nullable=True), +) diff --git a/src/skyline_apiserver/main.py b/src/skyline_apiserver/main.py new file mode 100644 index 0000000..4372332 --- /dev/null +++ b/src/skyline_apiserver/main.py @@ -0,0 +1,61 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from pathlib import Path + +from fastapi import FastAPI +from skyline_log import LOG, setup as log_setup +from starlette.middleware.cors import CORSMiddleware + +from skyline_apiserver.api.v1 import api_router +from skyline_apiserver.config import CONF, configure +from skyline_apiserver.db import setup as db_setup +from skyline_apiserver.policies import setup as policies_setup + +PROJECT_NAME = "Skyline API" +API_PREFIX = "/api/v1" + + +async def on_startup() -> None: + configure("skyline-apiserver") + log_setup(Path(CONF.default.log_dir).joinpath("skyline", "skyline-apiserver.log")) + policies_setup() + await db_setup() + + # Set all CORS enabled origins + if CONF.default.cors_allow_origins: + app.add_middleware( + CORSMiddleware, + allow_origins=[str(origin) for origin in CONF.default.cors_allow_origins], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + LOG.debug("Skyline API server start") + + +async def on_shutdown() -> None: + LOG.debug("Skyline API server stop") + + +app = FastAPI( + title=PROJECT_NAME, + openapi_url=f"{API_PREFIX}/openapi.json", + on_startup=[on_startup], + on_shutdown=[on_shutdown], +) + +app.include_router(api_router, prefix=API_PREFIX) diff --git a/src/skyline_apiserver/network/__init__.py b/src/skyline_apiserver/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/network/neutron.py b/src/skyline_apiserver/network/neutron.py new file mode 100644 index 0000000..f3019f5 --- /dev/null +++ b/src/skyline_apiserver/network/neutron.py @@ -0,0 +1,53 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict + +import six +from six.moves.urllib import parse + +from skyline_apiserver.types import constants +from skyline_apiserver.utils.httpclient import get_assert_200 + + +async def get_ports( + neutron_endpoint: str, + keystone_token: str, + search_opts: Dict[str, Any], +) -> Dict[str, Any]: + """Get the ports in the environment . + + :param neutron_endpoint: Nova endpoint in specified region. + :type neutron_endpoint: str + :param keystone_token: Keystone token. + :type keystone_token: str + :param search_opts: Search opts. + :type search_opts: dict + :return: ports. + :rtype: Dict[str, Any] + """ + url = neutron_endpoint + constants.NEUTRON_PORTS_API + qparams = {} + for opt, val in search_opts.items(): + if val: + if isinstance(val, six.text_type): + val = val.encode("utf-8") + if isinstance(val, list): + val = [v.encode("utf-8") for v in val] + qparams[opt] = val + url += "?%s" % parse.urlencode(qparams, doseq=True) + resp = await get_assert_200(url, headers={"X-Auth-Token": keystone_token}) + return resp.json() diff --git a/src/skyline_apiserver/policies/__init__.py b/src/skyline_apiserver/policies/__init__.py new file mode 100644 index 0000000..24624c7 --- /dev/null +++ b/src/skyline_apiserver/policies/__init__.py @@ -0,0 +1,39 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from skyline_policy_manager.policies import get_service_rules +from skyline_policy_manager.policies.base import APIRule + +from .base import Enforcer, UserContext + +ENFORCER = Enforcer() + + +def setup() -> None: + service_rules = get_service_rules() + all_api_rules = [] + for rules in service_rules.values(): + api_rules = [rule for rule in rules if isinstance(rule, APIRule)] + all_api_rules.extend(api_rules) + + ENFORCER.register_rules(all_api_rules) + + +__all__ = ( + "ENFORCER", + "UserContext", + "setup", +) diff --git a/src/skyline_apiserver/policies/base.py b/src/skyline_apiserver/policies/base.py new file mode 100644 index 0000000..9fbbaa7 --- /dev/null +++ b/src/skyline_apiserver/policies/base.py @@ -0,0 +1,116 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from collections.abc import MutableMapping +from typing import Any, Dict, Iterable, Iterator + +import attr +from immutables import Map +from keystoneauth1.access.access import AccessInfoV3 +from oslo_policy._checks import _check +from skyline_policy_manager.policies.base import APIRule + +from skyline_apiserver.config import CONF + + +class UserContext(MutableMapping): + def __init__( + self, + access: AccessInfoV3, + ): + self._data = {} + self.access = access + self._data.setdefault("auth_token", getattr(access, "auth_token", None)) + self._data.setdefault("user_id", getattr(access, "user_id", None)) + self._data.setdefault("project_id", getattr(access, "project_id", None)) + self._data.setdefault("domain_id", getattr(access, "domain_id", None)) + self._data.setdefault("user_domain_id", getattr(access, "user_domain_id", None)) + self._data.setdefault("project_domain_id", getattr(access, "project_domain_id", None)) + self._data.setdefault("username", getattr(access, "username", None)) + self._data.setdefault("project_name", getattr(access, "project_name", None)) + self._data.setdefault("domain_name", getattr(access, "domain_name", None)) + self._data.setdefault("user_domain_name", getattr(access, "user_domain_name", None)) + self._data.setdefault("project_domain_name", getattr(access, "project_domain_name", None)) + self._data.setdefault("system_scope", getattr(access, "system_scope", None)) + self._data.setdefault("role_ids", getattr(access, "role_ids", [])) + self._data.setdefault("roles", getattr(access, "role_names", [])) + + is_admin = False + for role in CONF.openstack.system_admin_roles: + if role in self._data["roles"]: + is_admin = True + break + + self._data.setdefault("is_admin", is_admin) + + is_reader_admin = False + for role in CONF.openstack.system_reader_roles: + if role in self._data["roles"]: + is_reader_admin = True + break + + self._data.setdefault("is_reader_admin", is_reader_admin) + + def __getitem__(self, k: Any) -> Any: + return self._data[k] + + def __setitem__(self, k: Any, v: Any) -> None: + self._data[k] = v + + def __delitem__(self, k: Any) -> None: + del self._data[k] + + def __iter__(self) -> Iterator[Any]: + return iter(self._data) + + def __len__(self) -> int: + return len(self._data) + + def __str__(self) -> str: + return self._data.__str__() + + def __repr__(self) -> str: + return self._data.__repr__() + + +@attr.s(kw_only=True, repr=True, frozen=False, slots=True, auto_attribs=True) +class Enforcer: + rules: Map = attr.ib(factory=Map, repr=True, init=False) + + def register_rules(self, rules: Iterable[APIRule]) -> None: + rule_map = {} + for rule in rules: + if rule.name in rule_map: + raise ValueError(f"Duplicate policy rule {rule.name}.") + + rule_map[rule.name] = rule.basic_check + + self.rules = Map(rule_map) + + def authorize(self, rule: str, target: Dict[str, Any], context: UserContext) -> bool: + result = False + do_check = self.rules.get(rule) + if do_check is None: + raise ValueError(f"Policy {rule} not registered.") + + result = _check( + rule=do_check, + target=target, + creds=context, + enforcer=self, + current_rule=rule, + ) + return result diff --git a/src/skyline_apiserver/schemas/__init__.py b/src/skyline_apiserver/schemas/__init__.py new file mode 100644 index 0000000..527cc67 --- /dev/null +++ b/src/skyline_apiserver/schemas/__init__.py @@ -0,0 +1,73 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import common +from .contrib import ContribListKeystoneEndpointsResponseModel +from .extension import ( + ExtListComputeServicesResponse, + ExtListPortsResponse, + ExtListRecycleServersResponse, + ExtListServersResponse, + ExtListVolumeSnapshotsResponse, + ExtListVolumesResponse, + ExtPortDeviceOwner, + ExtPortSortKey, + ExtPortStatus, + ExtRecycleServerSortKey, + ExtServerSortKey, + ExtServerStatus, + ExtSortDir, + ExtVolumeSnapshotSortKey, + ExtVolumeSnapshotStatus, + ExtVolumeSortKey, + ExtVolumeStatus, +) +from .login import Credential, Domain, License, Payload, Profile, Project, Region, Role +from .policy import Policies, PoliciesRules +from .setting import Setting, Settings, UpdateSetting + +__all__ = ( + "common", + "ContribListKeystoneEndpointsResponseModel", + "Credential", + "Domain", + "ExtListComputeServicesResponse", + "ExtListPortsResponse", + "ExtListRecycleServersResponse", + "ExtListServersResponse", + "ExtListVolumeSnapshotsResponse", + "ExtListVolumesResponse", + "ExtPortDeviceOwner", + "ExtPortSortKey", + "ExtPortStatus", + "ExtRecycleServerSortKey", + "ExtServerSortKey", + "ExtServerStatus", + "ExtSortDir", + "ExtVolumeSnapshotSortKey", + "ExtVolumeSnapshotStatus", + "ExtVolumeSortKey", + "ExtVolumeStatus", + "License", + "Payload", + "Policies", + "PoliciesRules", + "Profile", + "Project", + "Region", + "Role", + "Setting", + "Settings", + "UpdateSetting", +) diff --git a/src/skyline_apiserver/schemas/common.py b/src/skyline_apiserver/schemas/common.py new file mode 100644 index 0000000..e29d976 --- /dev/null +++ b/src/skyline_apiserver/schemas/common.py @@ -0,0 +1,45 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pydantic import BaseModel + + +class OK(BaseModel): + code: int = 200 + message: str + title: str = "OK" + + +class BadRequestMessage(BaseModel): + detail: str + + +class UnprocessableEntityMessage(BaseModel): + detail: str + + +class UnauthorizedMessage(BaseModel): + detail: str + + +class ForbiddenMessage(BaseModel): + detail: str + + +class NotFoundMessage(BaseModel): + detail: str + + +class InternalServerErrorMessage(BaseModel): + detail: str diff --git a/src/skyline_apiserver/schemas/contrib.py b/src/skyline_apiserver/schemas/contrib.py new file mode 100644 index 0000000..c8001f4 --- /dev/null +++ b/src/skyline_apiserver/schemas/contrib.py @@ -0,0 +1,22 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from pydantic import BaseModel + + +class ContribListKeystoneEndpointsResponseModel(BaseModel): + region_name: str + url: str diff --git a/src/skyline_apiserver/schemas/extension.py b/src/skyline_apiserver/schemas/extension.py new file mode 100644 index 0000000..2d90ddb --- /dev/null +++ b/src/skyline_apiserver/schemas/extension.py @@ -0,0 +1,517 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel, Field +from pydantic.types import UUID4 + +SERVERS_LIST_DOCS_LINKS = "https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detailed-detail#list-servers-detailed" # noqa +VOLUMES_LIST_DOCS_LINKS = "https://docs.openstack.org/api-ref/block-storage/v3/index.html?expanded=list-accessible-volumes-with-details-detail#list-accessible-volumes-with-details" # noqa +VOLUME_SNAPSHOTS_LIST_DOCS_LINKS = "https://docs.openstack.org/api-ref/block-storage/v3/index.html?expanded=list-snapshots-and-details-detail#list-snapshots-and-details" # noqa +PORTS_LIST_DOCS_LINKS = "https://docs.openstack.org/api-ref/network/v2/index.html?expanded=list-ports-detail#list-ports" # noqa + + +class ExtServerStatus(str, Enum): + ACTIVE = "ACTIVE" + BUILD = "BUILD" + # DELETED = "DELETED" + ERROR = "ERROR" + HARD_REBOOT = "HARD_REBOOT" + MIGRATING = "MIGRATING" + # PASSWORD = "PASSWORD" + PAUSED = "PAUSED" + REBOOT = "REBOOT" + REBUILD = "REBUILD" + RESCUE = "RESCUE" + RESIZE = "RESIZE" + # REVERT_RESIZE = "REVERT_RESIZE" + SHELVED = "SHELVED" + SHELVED_OFFLOADED = "SHELVED_OFFLOADED" + SHUTOFF = "SHUTOFF" + SOFT_DELETED = "SOFT_DELETED" + SUSPENDED = "SUSPENDED" + UNKNOWN = "UNKNOWN" + # VERIFY_RESIZE = "VERIFY_RESIZE" + + def __str__(self): + return self.value + + +class ExtVolumeStatus(str, Enum): + creating = "creating" + available = "available" + reserved = "reserved" + attaching = "attaching" + detaching = "detaching" + in_use = "in-use" + maintenance = "maintenance" + deleting = "deleting" + awaiting_transfer = "awaiting-transfer" + error = "error" + error_deleting = "error_deleting" + backing_up = "backing-up" + restoring_backup = "restoring-backup" + error_backing_up = "error_backing-up" + error_restoring = "error_restoring" + error_extending = "error_extending" + downloading = "downloading" + uploading = "uploading" + retyping = "retyping" + extending = "extending" + + def __str__(self): + return self.value + + +class ExtVolumeSnapshotStatus(str, Enum): + CREATING = "CREATING" + AVAILABLE = "AVAILABLE" + # BACKING_UP = "BACKING_UP" + DELETING = "DELETING" + ERROR = "ERROR" + # DELETED = "DELETED" + # UNMANAGING = "UNMANAGING" + # RESTORING = "RESTORING" + ERROR_DELETING = "ERROR_DELETING" + + def __str__(self): + return self.value + + +class ExtPortStatus(str, Enum): + ACTIVE = "ACTIVE" + DOWN = "DOWN" + BUILD = "BUILD" + ERROR = "ERROR" + N_A = "N/A" + + def __str__(self): + return self.value + + +class ExtPortDeviceOwner(str, Enum): + null = "" + compute_nova = "compute:nova" + network_dhcp = "network:dhcp" + network_floatingip = "network:floatingip" + network_router_gateway = "network:router_gateway" + network_router_ha_interface = "network:router_ha_interface" + network_ha_router_replicated_interface = "network:ha_router_replicated_interface" + + def __str__(self): + return self.value + + +class ExtSortDir(str, Enum): + desc = "desc" + asc = "asc" + + def __str__(self): + return self.value + + +class ExtServerSortKey(str, Enum): + uuid = "uuid" + display_name = "display_name" + vm_state = "vm_state" + locked = "locked" + created_at = "created_at" + host = "host" + project_id = "project_id" + + def __str__(self): + return self.value + + +class ExtRecycleServerSortKey(str, Enum): + uuid = "uuid" + display_name = "display_name" + updated_at = "updated_at" + project_id = "project_id" + + def __str__(self): + return self.value + + +class ExtVolumeSortKey(str, Enum): + id = "id" + name = "name" + size = "size" + status = "status" + bootable = "bootable" + created_at = "created_at" + + def __str__(self): + return self.value + + +class ExtVolumeSnapshotSortKey(str, Enum): + id = "id" + name = "name" + status = "status" + created_at = "created_at" + + def __str__(self): + return self.value + + +class ExtPortSortKey(str, Enum): + id = "id" + name = "name" + mac_address = "mac_address" + status = "status" + project_id = "project_id" + + def __str__(self): + return self.value + + +class ExtFlavor(BaseModel): + ephemeral: Optional[int] + ram: Optional[int] + original_name: Optional[str] + vcpus: Optional[int] + extra_specs: Optional[Dict[str, Any]] + swap: Optional[int] + disk: Optional[int] + + +class ExtListServersBaseResponse(BaseModel): + id: UUID4 + origin_data: Dict[str, Any] = Field( + description=f"The origin_data is the same like the response of {SERVERS_LIST_DOCS_LINKS}", + ) + project_name: Optional[str] + image: Optional[UUID4] + image_name: Optional[str] + image_os_distro: Optional[str] + fixed_addresses: Optional[List] + floating_addresses: Optional[List] + + name: Optional[str] = Field( + description="Will be removed, please use origin_data[name]", + deprecated=True, + ) + project_id: Optional[str] = Field( + description="Will be removed, please use origin_data[tenant_id]", + deprecated=True, + ) + host: Optional[str] = Field( + description="Will be removed, please use origin_data[OS-EXT-SRV-ATTR:host]", + deprecated=True, + ) + hostname: Optional[str] = Field( + description="Will be removed, please use origin_data[OS-EXT-SRV-ATTR:hostname]", + deprecated=True, + ) + flavor: Optional[str] = Field( + description="Will be removed, please use origin_data[flavor][original_name]", + deprecated=True, + ) + flavor_info: Optional[ExtFlavor] = Field( + description="Will be removed, please use origin_data[flavor]", + deprecated=True, + ) + status: Optional[str] = Field( + description="Will be removed, please use origin_data[status]", + deprecated=True, + ) + locked: Optional[bool] = Field( + description="Will be removed, please use origin_data[locked]", + deprecated=True, + ) + created_at: Optional[str] = Field( + description="Will be removed, please use origin_data[created]", + deprecated=True, + ) + task_state: Optional[str] = Field( + description="Will be removed, please use origin_data[OS-EXT-STS:task_state]", + deprecated=True, + ) + vm_state: Optional[str] = Field( + description="Will be removed, please use origin_data[OS-EXT-STS:vm_state]", + deprecated=True, + ) + power_state: Optional[int] = Field( + description="Will be removed, please use origin_data[OS-EXT-STS:power_state]", + deprecated=True, + ) + root_device_name: Optional[str] = Field( + description="Will be removed, please use origin_data[OS-EXT-SRV-ATTR:root_device_name]", + deprecated=True, + ) + metadata: Optional[Dict[str, Any]] = Field( + description="Will be removed, please use origin_data[metadata]", + deprecated=True, + ) + + +class ExtListServersResponse(BaseModel): + servers: List[ExtListServersBaseResponse] + + +class ExtListRecycleServersBaseResponse(BaseModel): + id: UUID4 + origin_data: Dict[str, Any] = Field( + description=f"The origin_data is the same like the response of {SERVERS_LIST_DOCS_LINKS}", + ) + project_name: Optional[str] + image: Optional[UUID4] + image_name: Optional[str] + image_os_distro: Optional[str] + fixed_addresses: Optional[List] + floating_addresses: Optional[List] + deleted_at: Optional[str] + reclaim_timestamp: float + + name: Optional[str] = Field( + description="Will be removed, please use origin_data[name]", + deprecated=True, + ) + project_id: Optional[str] = Field( + description="Will be removed, please use origin_data[tenant_id]", + deprecated=True, + ) + host: Optional[str] = Field( + description="Will be removed, please use origin_data[OS-EXT-SRV-ATTR:host]", + deprecated=True, + ) + hostname: Optional[str] = Field( + description="Will be removed, please use origin_data[OS-EXT-SRV-ATTR:hostname]", + deprecated=True, + ) + flavor: Optional[str] = Field( + description="Will be removed, please use origin_data[flavor][original_name]", + deprecated=True, + ) + flavor_info: Optional[ExtFlavor] = Field( + description="Will be removed, please use origin_data[flavor]", + deprecated=True, + ) + status: Optional[str] = Field( + description="Will be removed, please use origin_data[status]", + deprecated=True, + ) + + +class ExtListRecycleServersResponse(BaseModel): + recycle_servers: List[ExtListRecycleServersBaseResponse] + + +class VolumeAttachment(BaseModel): + id: str + device: Optional[str] + server_id: Optional[str] + server_name: Optional[str] + + +class ExtListVolumesBaseResponse(BaseModel): + id: UUID4 + origin_data: Dict[str, Any] = Field( + description=f"The origin_data is the same like the response of {VOLUMES_LIST_DOCS_LINKS}", + ) + project_name: Optional[str] + attachments: Optional[List[VolumeAttachment]] + + name: Optional[str] = Field( + description="Will be removed, please use origin_data[name]", + deprecated=True, + ) + project_id: Optional[str] = Field( + description="Will be removed, please use origin_data[os-vol-tenant-attr:tenant_id]", + deprecated=True, + ) + host: Optional[str] = Field( + description="Will be removed, please use origin_data[os-vol-host-attr:host]", + deprecated=True, + ) + snapshot_id: Optional[str] = Field( + description="Will be removed, please use origin_data[snapshot_id]", + deprecated=True, + ) + source_volid: Optional[str] = Field( + description="Will be removed, please use origin_data[source_volid]", + deprecated=True, + ) + size: Optional[int] = Field( + description="Will be removed, please use origin_data[size]", + deprecated=True, + ) + status: Optional[str] = Field( + description="Will be removed, please use origin_data[status]", + deprecated=True, + ) + volume_type: Optional[str] = Field( + description="Will be removed, please use origin_data[volume_type]", + deprecated=True, + ) + encrypted: Optional[bool] = Field( + description="Will be removed, please use origin_data[encrypted]", + deprecated=True, + ) + bootable: Optional[str] = Field( + description="Will be removed, please use origin_data[bootable]", + deprecated=True, + ) + multiattach: Optional[bool] = Field( + description="Will be removed, please use origin_data[multiattach]", + deprecated=True, + ) + availability_zone: Optional[str] = Field( + description="Will be removed, please use origin_data[availability_zone]", + deprecated=True, + ) + created_at: Optional[str] = Field( + description="Will be removed, please use origin_data[created_at]", + deprecated=True, + ) + + +class ExtListVolumesResponse(BaseModel): + count: int = 0 + volumes: List[ExtListVolumesBaseResponse] + + +class ExtListVolumeSnapshotsBaseResponse(BaseModel): + id: str + origin_data: Dict[str, Any] = Field( + description=f"The origin_data is the same like the response of {VOLUME_SNAPSHOTS_LIST_DOCS_LINKS}", # noqa + ) + project_name: Optional[str] + host: Optional[str] + volume_name: Optional[str] + child_volumes: Optional[List] + + name: Optional[str] = Field( + description="Will be removed, please use origin_data[name]", + deprecated=True, + ) + project_id: Optional[str] = Field( + description="Will be removed, please use origin_data[os-extended-snapshot-attributes:project_id]", # noqa + deprecated=True, + ) + size: Optional[int] = Field( + description="Will be removed, please use origin_data[size]", + deprecated=True, + ) + status: Optional[str] = Field( + description="Will be removed, please use origin_data[status]", + deprecated=True, + ) + volume_id: Optional[str] = Field( + description="Will be removed, please use origin_data[volume_id]", + deprecated=True, + ) + created_at: Optional[str] = Field( + description="Will be removed, please use origin_data[created_at]", + deprecated=True, + ) + metadata: Optional[Dict[Any, Any]] = Field( + description="Will be removed, please use origin_data[metadata]", + deprecated=True, + ) + + +class ExtListVolumeSnapshotsResponse(BaseModel): + count: int = 0 + volume_snapshots: List[ExtListVolumeSnapshotsBaseResponse] + + +class ExtListPortsBaseResponse(BaseModel): + id: str + origin_data: Dict[str, Any] = Field( + description=f"The origin_data is the same like the response of {PORTS_LIST_DOCS_LINKS}", # noqa + ) + server_name: Optional[str] + network_name: Optional[str] + ipv4: Optional[List] + ipv6: Optional[List] + + name: Optional[str] = Field( + description="Will be removed, please use origin_data[name]", + deprecated=True, + ) + mac_address: Optional[str] = Field( + description="Will be removed, please use origin_data[mac_address]", + deprecated=True, + ) + project_id: Optional[str] = Field( + description="Will be removed, please use origin_data[project_id]", + deprecated=True, + ) + device_owner: Optional[str] = Field( + description="Will be removed, please use origin_data[device_owner]", + deprecated=True, + ) + device_id: Optional[str] = Field( + description="Will be removed, please use origin_data[device_id]", + deprecated=True, + ) + status: Optional[str] = Field( + description="Will be removed, please use origin_data[status]", + deprecated=True, + ) + created_at: Optional[str] = Field( + description="Will be removed, please use origin_data[created_at]", + deprecated=True, + ) + network_id: Optional[str] = Field( + description="Will be removed, please use origin_data[network_id]", + deprecated=True, + ) + binding_vnic_type: Optional[str] = Field( + description="Will be removed, please use origin_data[binding:vnic_type]", + deprecated=True, + ) + description: Optional[str] = Field( + description="Will be removed, please use origin_data[description]", + deprecated=True, + ) + port_security_enabled: Optional[bool] = Field( + description="Will be removed, please use origin_data[port_security_enabled]", + deprecated=True, + ) + qos_policy_id: Optional[str] = Field( + description="Will be removed, please use origin_data[qos_policy_id]", + deprecated=True, + ) + fixed_ips: Optional[List] = Field( + description="Will be removed, please use origin_data[fixed_ips]", + deprecated=True, + ) + + +class ExtListPortsResponse(BaseModel): + count: int = 0 + ports: List[ExtListPortsBaseResponse] + + +class ExtListComputeServicesBaseResponse(BaseModel): + id: Optional[str] + binary: str + disabled_reason: Optional[str] + host: str + state: Optional[str] + status: str + updated_at: Optional[str] + forced_down: Optional[bool] + zone: Optional[str] + + +class ExtListComputeServicesResponse(BaseModel): + services: List[ExtListComputeServicesBaseResponse] diff --git a/src/skyline_apiserver/schemas/login.py b/src/skyline_apiserver/schemas/login.py new file mode 100644 index 0000000..5ae1bc6 --- /dev/null +++ b/src/skyline_apiserver/schemas/login.py @@ -0,0 +1,126 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict, List, Optional + +from jose import jwt +from pydantic import BaseModel + +from skyline_apiserver import config +from skyline_apiserver.types import constants + + +class Credential(BaseModel): + region: str + domain: str + username: str + password: str + + class Config: + schema_extra = { + "example": { + "region": "RegionOne", + "username": "admin", + "domain": "default", + "password": "admin", + }, + } + + +class Domain(BaseModel): + id: str + name: str + + +class License(BaseModel): + name: str + summary: str + macs: List[str] + features: List[Dict[str, Any]] + start: str + end: str + + +class Region(BaseModel): + id: str + + +class Role(BaseModel): + id: str + name: str + + +class Project(BaseModel): + id: str + name: str + domain: Domain + + +class User(BaseModel): + id: str + name: str + domain: Domain + + +class Payload(BaseModel): + keystone_token: str + region: str + exp: int + uuid: str + + def toDict(self) -> Dict[str, Any]: + return { + "keystone_token": self.keystone_token, + "region": self.region, + "exp": self.exp, + "uuid": self.uuid, + } + + def toJWTPayload(self) -> str: + return jwt.encode( + self.toDict(), + config.CONF.default.secret_key, + algorithm=constants.ALGORITHM, + ) + + +class Profile(BaseModel): + keystone_token: str + region: str + project: Project + user: User + roles: List[Role] + keystone_token_exp: str + base_roles: Optional[List[str]] + base_domains: Optional[List[str]] + endpoints: Optional[Dict[str, Any]] + projects: Optional[Dict[str, Any]] + exp: int + uuid: str + version: str + license: Optional[License] + currency: Optional[Dict[str, str]] + + def toPayLoad(self) -> Payload: + return Payload( + keystone_token=self.keystone_token, + region=self.region, + exp=self.exp, + uuid=self.uuid, + ) + + def toJWTPayload(self) -> str: + return self.toPayLoad().toJWTPayload() diff --git a/src/skyline_apiserver/schemas/policy.py b/src/skyline_apiserver/schemas/policy.py new file mode 100644 index 0000000..8179ba6 --- /dev/null +++ b/src/skyline_apiserver/schemas/policy.py @@ -0,0 +1,32 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import List + +from pydantic import BaseModel + + +class Policy(BaseModel): + rule: str + allowed: bool + + +class Policies(BaseModel): + policies: List[Policy] + + +class PoliciesRules(BaseModel): + rules: List[str] diff --git a/src/skyline_apiserver/schemas/setting.py b/src/skyline_apiserver/schemas/setting.py new file mode 100644 index 0000000..f493f76 --- /dev/null +++ b/src/skyline_apiserver/schemas/setting.py @@ -0,0 +1,33 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, List, Optional + +from pydantic import BaseModel + + +class Setting(BaseModel): + key: str + value: Any + hidden: Optional[bool] + restart_service: bool + + +class UpdateSetting(BaseModel): + key: str + value: Any + + +class Settings(BaseModel): + settings: List[Setting] diff --git a/src/skyline_apiserver/types/__init__.py b/src/skyline_apiserver/types/__init__.py new file mode 100644 index 0000000..803b135 --- /dev/null +++ b/src/skyline_apiserver/types/__init__.py @@ -0,0 +1,33 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from enum import Enum +from typing import Any, Callable, Dict, Optional, TypeVar + +# Function types +Fn = TypeVar("Fn", bound=Callable[..., Any]) + + +class InterfaceType(str, Enum): + internal = "internal" + admin = "admin" + public = "public" + + +SchemaT = Dict[str, Any] +EnvT = Optional[Dict[str, str]] + +__all__ = ("SchemaT", "EnvT", "Fn") diff --git a/src/skyline_apiserver/types/constants.py b/src/skyline_apiserver/types/constants.py new file mode 100644 index 0000000..7921766 --- /dev/null +++ b/src/skyline_apiserver/types/constants.py @@ -0,0 +1,41 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +VERSION = "8.1.0" + +ALGORITHM = "HS256" + +KEYSTONE_API_VERSION = "3.13" +NOVA_API_VERSION = "2.79" +GLANCE_API_VERSION = "2" +CINDER_API_VERSION = "3.59" +NEUTRON_API_VERSION = "2.0" +PLACEMENT_API_VERSION = "1.36" + +ERR_MSG_TOKEN_REVOKED = "The token has revoked." +ERR_MSG_TOKEN_EXPIRED = "The token has expired." +ERR_MSG_TOKEN_NOTFOUND = "Token not found." + +# RESTful API +# neutron +NEUTRON_PORTS_API = "/v2.0/ports" + +EXTENSION_API_LIMIT_GT = 0 + +ID_UUID_RANGE_STEP = 100 + +SETTINGS_HIDDEN_SET = set(["license", "currency"]) +SETTINGS_RESTART_SET = set(["license", "currency"]) + +ADDONS_DEFAULT = ["firewall", "loadbalance"] diff --git a/src/skyline_apiserver/utils/__init__.py b/src/skyline_apiserver/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/skyline_apiserver/utils/coroutine.py b/src/skyline_apiserver/utils/coroutine.py new file mode 100644 index 0000000..bfa466d --- /dev/null +++ b/src/skyline_apiserver/utils/coroutine.py @@ -0,0 +1,29 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import asyncio +from functools import wraps +from typing import Any + +from skyline_apiserver.types import Fn + + +def sync_run(fn: Fn) -> Fn: + @wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> Any: + return asyncio.run(fn(*args, **kwargs)) + + return wrapper diff --git a/src/skyline_apiserver/utils/httpclient.py b/src/skyline_apiserver/utils/httpclient.py new file mode 100644 index 0000000..9408c08 --- /dev/null +++ b/src/skyline_apiserver/utils/httpclient.py @@ -0,0 +1,94 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import types +from typing import Any, Dict + +import httpx +from fastapi import HTTPException, status +from httpx import Response, codes + + +async def _http_request( + method: types.FunctionType = httpx.AsyncClient.get, + **kwargs, +) -> Response: + async with httpx.AsyncClient(verify=False) as client: + try: + response = await method( + client, + **kwargs, + ) + return response + except Exception as ex: + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(ex)) + + +async def assert_http_request( + method: types.FunctionType, + expectedStatus: str = codes.OK, + **kwargs, +) -> Response: + response = await _http_request(method, **kwargs) + if response.status_code != expectedStatus: + raise HTTPException( + status_code=response.status_code, + detail=response.text, + ) + return response + + +async def get_assert_200( + url: str, + cookies: Dict[str, Any] = None, + headers: Dict[str, Any] = None, + params: Dict[str, Any] = None, +) -> Response: + return await assert_http_request( + method=httpx.AsyncClient.get, + expectedStatus=codes.OK, + url=url, + cookies=cookies, + headers=headers, + params=params, + ) + + +async def delete_assert_200(url, cookies: Dict[str, Any] = None) -> Response: + return await assert_http_request( + method=httpx.AsyncClient.delete, + expectedStatus=codes.OK, + url=url, + cookies=cookies, + ) + + +async def post_assert_201(url: str, json: Dict[str, Any], cookies: Dict[str, Any]) -> Response: + return await assert_http_request( + method=httpx.AsyncClient.post, + expectedStatus=codes.CREATED, + url=url, + json=json, + cookies=cookies, + ) + + +async def put_assert_200(url: str, json: Dict[str, Any], cookies: Dict[str, Any]) -> Response: + return await assert_http_request( + method=httpx.AsyncClient.put, + expectedStatus=codes.OK, + url=url, + json=json, + cookies=cookies, + ) diff --git a/src/skyline_apiserver/utils/jsonschema.py b/src/skyline_apiserver/utils/jsonschema.py new file mode 100644 index 0000000..c944f2c --- /dev/null +++ b/src/skyline_apiserver/utils/jsonschema.py @@ -0,0 +1,24 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from functools import partial + +import jsonschema + +format_checker = jsonschema.FormatChecker() +validate = partial(jsonschema.validate, format_checker=format_checker) + +__all__ = ("validate",) diff --git a/src/skyline_apiserver/utils/roles.py b/src/skyline_apiserver/utils/roles.py new file mode 100644 index 0000000..90706d2 --- /dev/null +++ b/src/skyline_apiserver/utils/roles.py @@ -0,0 +1,59 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from fastapi import HTTPException, status + +from skyline_apiserver import schemas +from skyline_apiserver.config import CONF + + +def is_system_admin(profile: schemas.Profile) -> bool: + roles = set(role.name for role in profile.roles) + if roles & set(CONF.openstack.system_admin_roles): + return True + return False + + +def is_system_reader_no_admin(profile: schemas.Profile) -> bool: + roles = set(role.name for role in profile.roles) + if (roles & set(CONF.openstack.system_reader_roles)) and ( + not roles & set(CONF.openstack.system_admin_roles) + ): + return True + return False + + +def is_system_admin_or_reader(profile: schemas.Profile) -> bool: + roles = set(role.name for role in profile.roles) + if roles & set(CONF.openstack.system_admin_roles + CONF.openstack.system_reader_roles): + return True + return False + + +def assert_system_admin(profile: schemas.Profile, exception: str) -> None: + if not is_system_admin(profile): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=exception, + ) + + +def assert_system_admin_or_reader(profile: schemas.Profile, exception: str) -> None: + if not is_system_admin_or_reader(profile): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=exception, + ) diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/v1/__init__.py b/tests/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/v1/test_contrib.py b/tests/api/v1/test_contrib.py new file mode 100644 index 0000000..424d440 --- /dev/null +++ b/tests/api/v1/test_contrib.py @@ -0,0 +1,49 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import pytest +from httpx import AsyncClient + +from skyline_apiserver import main +from skyline_apiserver.config import CONF + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_get_regions(client: AsyncClient) -> None: + r = await client.get(url=f"{main.API_PREFIX}/contrib/regions") + result = r.json() + assert r.status_code == 200 + assert len(result) > 0 + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_get_domains(client: AsyncClient) -> None: + r = await client.get(url=f"{main.API_PREFIX}/contrib/domains") + result = r.json() + assert r.status_code == 200 + assert len(result) > 0 + assert result[0] not in CONF.openstack.base_domains + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_keystone_endpoints(client: AsyncClient) -> None: + r = await client.get(url=f"{main.API_PREFIX}/contrib/keystone_endpoints") + result = r.json() + assert r.status_code == 200 + assert len(result) > 0 diff --git a/tests/api/v1/test_extension.py b/tests/api/v1/test_extension.py new file mode 100644 index 0000000..76671a9 --- /dev/null +++ b/tests/api/v1/test_extension.py @@ -0,0 +1,278 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid +from typing import Any, Dict + +import pytest +from httpx import AsyncClient +from six.moves.urllib import parse + +from skyline_apiserver import main + + +@pytest.mark.parametrize( + ("params", "status_code"), + ( + ({}, 200), + ({"limit": 10}, 200), + ({"limit": 20, "marker": str(uuid.uuid4())}, 400), + ({"sort_dirs": "desc", "sort_keys": "uuid"}, 200), + ({"sort_dirs": "desc", "sort_keys": "display_name"}, 200), + ({"sort_dirs": "desc", "sort_keys": "vm_state"}, 200), + ({"sort_dirs": "desc", "sort_keys": "locked"}, 200), + ({"sort_dirs": "desc", "sort_keys": "created_at"}, 200), + ({"sort_dirs": "desc", "sort_keys": "host"}, 200), + ({"sort_dirs": "desc", "sort_keys": "project_id"}, 200), + ({"sort_dirs": "desc", "sort_keys": "abc123"}, 422), + ({"all_projects": True}, 200), + ({"all_projects": True, "project_id": str(uuid.uuid4())}, 200), + ({"all_projects": True, "project_name": "test-project"}, 200), + ( + { + "all_projects": True, + "project_id": str(uuid.uuid4()), + "project_name": "test-project", + }, + 200, + ), + ({"name": "abc123"}, 200), + ({"status": "ACTIVE"}, 200), + ({"host": "host01"}, 200), + ({"flavor_id": "abc123"}, 200), + ({"uuid": [str(uuid.uuid4())]}, 200), + ), +) +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_servers( + client: AsyncClient, + login_jwt: str, + params: Dict[str, Any], + status_code: int, +) -> None: + # list servers + url = f"{main.API_PREFIX}/extension/servers" + "?%s" % parse.urlencode(params) + r = await client.get( + url=url, + headers={"Content-Type": "application/json"}, + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == status_code + if status_code == 200: + assert "servers" in result + + +@pytest.mark.parametrize( + ("params", "status_code"), + ( + ({}, 200), + ({"limit": 10}, 200), + ({"limit": 20, "marker": str(uuid.uuid4())}, 400), + ({"sort_dirs": "desc", "sort_keys": "uuid"}, 200), + ({"sort_dirs": "desc", "sort_keys": "display_name"}, 200), + ({"sort_dirs": "desc", "sort_keys": "updated_at"}, 200), + ({"sort_dirs": "desc", "sort_keys": "project_id"}, 200), + ({"sort_dirs": "desc", "sort_keys": "abc123"}, 422), + ({"all_projects": True}, 200), + ({"all_projects": True, "project_id": str(uuid.uuid4())}, 200), + ({"all_projects": True, "project_name": "test-project"}, 200), + ( + { + "all_projects": True, + "project_id": str(uuid.uuid4()), + "project_name": "test-project", + }, + 200, + ), + ({"name": "abc123"}, 200), + ({"uuid": [str(uuid.uuid4())]}, 200), + ), +) +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_recycle_servers( + client: AsyncClient, + login_jwt: str, + params: Dict[str, Any], + status_code: int, +) -> None: + # list recycle servers + url = f"{main.API_PREFIX}/extension/recycle_servers" + "?%s" % parse.urlencode(params) + r = await client.get( + url=url, + headers={"Content-Type": "application/json"}, + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == status_code + if status_code == 200: + assert "recycle_servers" in result + + +@pytest.mark.parametrize( + ("params", "status_code"), + ( + ({}, 200), + ({"limit": 10}, 200), + ({"limit": 20, "marker": str(uuid.uuid4())}, 500), + ({"sort_dirs": "desc", "sort_keys": "id"}, 200), + ({"sort_dirs": "desc", "sort_keys": "name"}, 200), + ({"sort_dirs": "desc", "sort_keys": "size"}, 200), + ({"sort_dirs": "desc", "sort_keys": "status"}, 200), + ({"sort_dirs": "desc", "sort_keys": "bootable"}, 200), + ({"sort_dirs": "desc", "sort_keys": "created_at"}, 200), + ({"sort_dirs": "desc", "sort_keys": "abc123"}, 422), + ({"all_projects": True}, 200), + ({"all_projects": True, "project_id": str(uuid.uuid4())}, 200), + ({"name": "abc123"}, 200), + ({"multiattach": True}, 200), + ({"status": "available"}, 200), + ({"uuid": [str(uuid.uuid4())]}, 200), + ), +) +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_volumes( + client: AsyncClient, + login_jwt: str, + params: Dict[str, Any], + status_code: int, +) -> None: + # list volumes + url = f"{main.API_PREFIX}/extension/volumes" + "?%s" % parse.urlencode(params) + r = await client.get( + url=url, + headers={"Content-Type": "application/json"}, + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == status_code + if status_code == 200: + assert "volumes" in result + assert "count" in result + + +@pytest.mark.parametrize( + ("params", "status_code"), + ( + ({}, 200), + ({"limit": 10}, 200), + ({"limit": 20, "marker": str(uuid.uuid4())}, 500), + ({"sort_dirs": "desc", "sort_keys": "id"}, 200), + ({"sort_dirs": "desc", "sort_keys": "name"}, 200), + ({"sort_dirs": "desc", "sort_keys": "status"}, 200), + ({"sort_dirs": "desc", "sort_keys": "created_at"}, 200), + ({"sort_dirs": "desc", "sort_keys": "abc123"}, 422), + ({"all_projects": True}, 200), + ({"all_projects": True, "project_id": str(uuid.uuid4())}, 200), + ({"name": "abc123"}, 200), + ({"status": "AVAILABLE"}, 200), + ({"volume_id": str(uuid.uuid4())}, 200), + ), +) +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_volume_snapshots( + client: AsyncClient, + login_jwt: str, + params: Dict[str, Any], + status_code: int, +) -> None: + # list volume snapshots + url = f"{main.API_PREFIX}/extension/volume_snapshots" + "?%s" % parse.urlencode(params) + r = await client.get( + url=url, + headers={"Content-Type": "application/json"}, + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == status_code + if status_code == 200: + assert "volume_snapshots" in result + assert "count" in result + + +@pytest.mark.parametrize( + ("params", "status_code"), + ( + ({}, 200), + ({"limit": 10}, 200), + ({"limit": 20, "marker": str(uuid.uuid4())}, 404), + ({"sort_dirs": "desc", "sort_keys": "id"}, 200), + ({"sort_dirs": "desc", "sort_keys": "name"}, 200), + ({"sort_dirs": "desc", "sort_keys": "mac_address"}, 200), + ({"sort_dirs": "desc", "sort_keys": "status"}, 200), + ({"sort_dirs": "desc", "sort_keys": "project_id"}, 200), + ({"sort_dirs": "desc", "sort_keys": "abc123"}, 422), + ({"all_projects": True}, 200), + ({"all_projects": True, "project_id": str(uuid.uuid4())}, 200), + ({"name": "abc123"}, 200), + ({"status": "ACTIVE"}, 200), + ({"network_name": "net01"}, 200), + ({"network_id": str(uuid.uuid4())}, 200), + ({"device_id": str(uuid.uuid4())}, 200), + ({"device_owner": "compute:nova"}, 200), + ({"uuid": [str(uuid.uuid4())]}, 200), + ), +) +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_ports( + client: AsyncClient, + login_jwt: str, + params: Dict[str, Any], + status_code: int, +) -> None: + # list ports + url = f"{main.API_PREFIX}/extension/ports" + "?%s" % parse.urlencode(params) + r = await client.get( + url=url, + headers={"Content-Type": "application/json"}, + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == status_code + if status_code == 200: + assert "ports" in result + + +@pytest.mark.parametrize( + ("params", "status_code"), + ( + ({}, 200), + ({"binary": "nova-compute"}, 200), + ({"host": "host01"}, 200), + ), +) +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_compute_services( + client: AsyncClient, + login_jwt: str, + params: Dict[str, Any], + status_code: int, +) -> None: + url = f"{main.API_PREFIX}/extension/compute-services" + "?%s" % parse.urlencode(params) + r = await client.get( + url=url, + headers={"Content-Type": "application/json"}, + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == status_code + if status_code == 200: + assert "services" in result diff --git a/tests/api/v1/test_login.py b/tests/api/v1/test_login.py new file mode 100644 index 0000000..8f899af --- /dev/null +++ b/tests/api/v1/test_login.py @@ -0,0 +1,135 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import time + +import pytest +from httpx import AsyncClient +from utils import utils + +from skyline_apiserver import main +from skyline_apiserver.config import CONF +from skyline_apiserver.db import api as db_api, setup as db_setup +from skyline_apiserver.types import constants + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_login(client: AsyncClient) -> None: + login_data = { + "region": "RegionOne", + "username": CONF.openstack.system_user_name, + "domain": CONF.openstack.system_user_domain, + "password": CONF.openstack.system_user_password, + } + r = await client.post( + url=f"{main.API_PREFIX}/login", + json=login_data, + ) + result = r.json() + assert r.status_code == 200 + assert "keystone_token" in result + assert "keystone" in result["endpoints"] + assert "projects" in result + await utils._logout(client) + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_get_profile_ok(client: AsyncClient, login_jwt: str) -> None: + r = await client.get( + f"{main.API_PREFIX}/profile", + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == 200 + assert "keystone_token" in result + assert "keystone" in result["endpoints"] + assert "projects" in result + assert "base_roles" in result + assert "base_domains" in result + assert "license" in result + assert "currency" in result + assert result["version"] == constants.VERSION + assert len(CONF.openstack.base_domains) == len(result["base_domains"]) + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_switch_project(client: AsyncClient, login_jwt: str) -> None: + r = await client.get( + f"{main.API_PREFIX}/profile", + cookies={"session": login_jwt}, + ) + result = r.json() + assert r.status_code == 200 + assert "project" in result + + project_id = result["project"]["id"] + r = await client.post( + f"{main.API_PREFIX}/switch_project/{project_id}", + ) + result = r.json() + assert r.status_code == 200 + assert project_id == result["project"]["id"] + await utils._logout(client) + + +@pytest.mark.asyncio +async def test_get_profile_no_auth(client: AsyncClient) -> None: + r = await client.get(f"{main.API_PREFIX}/profile") + result = r.json() + assert r.status_code == 401 + assert result["detail"] == constants.ERR_MSG_TOKEN_NOTFOUND + + +@pytest.mark.asyncio +async def test_get_profile_token_expire(client: AsyncClient) -> None: + profile = utils.get_session_profile() + profile.exp = int(time.time()) - 1 + token = profile.toJWTPayload() + + r = await client.get( + f"{main.API_PREFIX}/profile", + cookies={CONF.default.session_name: token}, + ) + result = r.json() + assert r.status_code == 401 + assert result["detail"].startswith(constants.ERR_MSG_TOKEN_EXPIRED) + + +@pytest.mark.asyncio +async def test_get_profile_token_revoke(client: AsyncClient) -> None: + profile = utils.get_session_profile() + await db_setup() + await db_api.revoke_token(profile.uuid, profile.exp) + token = profile.toJWTPayload() + + r = await client.get( + f"{main.API_PREFIX}/profile", + cookies={CONF.default.session_name: token}, + ) + result = r.json() + assert r.status_code == 401 + assert result["detail"] == constants.ERR_MSG_TOKEN_REVOKED + + +@pytest.mark.asyncio +async def test_logout(client: AsyncClient, session_token: str) -> None: + r = await client.post( + f"{main.API_PREFIX}/logout", + cookies={CONF.default.session_name: session_token}, + ) + assert r.status_code == 200 diff --git a/tests/api/v1/test_setting.py b/tests/api/v1/test_setting.py new file mode 100644 index 0000000..33d22ca --- /dev/null +++ b/tests/api/v1/test_setting.py @@ -0,0 +1,118 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import uuid + +import pytest +from httpx import AsyncClient + +from skyline_apiserver import main +from skyline_apiserver.config import CONF +from skyline_apiserver.types import constants + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_list_settings(client: AsyncClient, login_jwt: str) -> None: + result = await client.get( + f"{main.API_PREFIX}/settings", + cookies={"session": login_jwt}, + ) + assert result.status_code == 200 + assert "settings" in result.json() + settings = result.json()["settings"] + for setting in settings: + key = setting["key"] + assert setting["hidden"] == (key in constants.SETTINGS_HIDDEN_SET) + assert setting["restart_service"] == (key in constants.SETTINGS_RESTART_SET) + # settings not only include something in CONF.setting.base_settings + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_get_default_setting(client: AsyncClient, login_jwt: str) -> None: + for key in CONF.setting.base_settings: + result = await client.get( + f"{main.API_PREFIX}/setting/{key}", + cookies={"session": login_jwt}, + ) + assert result.status_code == 200 + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_get_setting_not_found(client: AsyncClient, login_jwt: str) -> None: + key = str(uuid.uuid4().hex) + result = await client.get( + f"{main.API_PREFIX}/setting/{key}", + cookies={"session": login_jwt}, + ) + assert result.status_code == 404 + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_update_setting(client: AsyncClient, login_jwt: str) -> None: + for key in CONF.setting.base_settings: + # Create + update_value = ["test1", "test2"] + result = await client.put( + url=f"{main.API_PREFIX}/setting", + json={"key": key, "value": update_value}, + cookies={"session": login_jwt}, + ) + assert result.status_code == 200 + assert result.json()["value"] == update_value + + # Update + update_value = ["test1", "test3"] + result = await client.put( + url=f"{main.API_PREFIX}/setting", + json={"key": key, "value": update_value}, + cookies={"session": login_jwt}, + ) + assert result.status_code == 200 + assert result.json()["value"] == update_value + break + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_update_setting_not_found(client: AsyncClient, login_jwt: str) -> None: + key = str(uuid.uuid4().hex) + update_value = {"value": "{}"} + result = await client.put( + url=f"{main.API_PREFIX}/setting", + json={"key": key, "value": update_value}, + cookies={"session": login_jwt}, + ) + assert result.status_code == 404 + + +@pytest.mark.skipif(os.getenv("TEST_API") != "true", reason="No backend OpenStack for api-test.") +@pytest.mark.asyncio +async def test_reset_setting(client: AsyncClient, login_jwt: str) -> None: + for key in CONF.setting.base_settings: + result = await client.delete( + f"{main.API_PREFIX}/setting/{key}", + cookies={"session": login_jwt}, + ) + assert result.status_code == 204 + result = await client.get( + f"{main.API_PREFIX}/setting/{key}", + cookies={"session": login_jwt}, + ) + assert result.json()["value"] == getattr(CONF.setting, key) + break diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..ed96bac --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,42 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Iterator + +import pytest +from asgi_lifespan import LifespanManager +from httpx import AsyncClient +from utils import utils + +from skyline_apiserver.config import CONF +from skyline_apiserver.main import app + + +@pytest.fixture(scope="function") +async def client() -> Iterator[AsyncClient]: + async with LifespanManager(app): + async with AsyncClient(app=app, base_url="http://test") as ac: + yield ac + + CONF.cleanup() + + +@pytest.fixture(scope="function") +async def session_token(client: AsyncClient) -> str: + return utils.get_session_token() + + +@pytest.fixture(scope="function") +async def login_jwt(client: AsyncClient) -> str: + return await utils.get_jwt_from_cookie(client) diff --git a/tests/core/__init__.py b/tests/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/core/config/__init__.py b/tests/core/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/core/config/backup_test_base.py b/tests/core/config/backup_test_base.py new file mode 100644 index 0000000..54b4d32 --- /dev/null +++ b/tests/core/config/backup_test_base.py @@ -0,0 +1,143 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import pytest +from jsonschema import ValidationError + +from skyline_apiserver.config import base + + +@pytest.mark.parametrize( + "opt", + [ + {"name": "test0", "help": "this is test0"}, + {"name": "test1", "help": "this is test1", "deprecated": True}, + {"name": "test2", "help": "this is test2", "schema": {"type": "string"}}, + {"name": "test3", "help": "this is test3", "default": "test3"}, + ], +) +@pytest.mark.parametrize("value", ["test_value"]) +def test_opt_from_init(opt, value): + opt = base.Opt(**opt) + assert opt._loaded is False + if opt.default is not None: + value = None + result = opt.default + else: + result = value + opt.load(value) + assert opt._loaded is True + assert opt.value == result + with pytest.raises(ValueError): + opt.load(value) + + +@pytest.mark.parametrize( + "opt,value", + [ + ({"name": "test0", "help": "this is test0", "schema": {"type": "null"}}, "test_value"), + ({"name": "test1", "help": "this is test1", "schema": {"type": "string"}}, None), + ({"name": "test2", "help": "this is test2", "schema": {"type": "array"}}, None), + ({"name": "test3", "help": "this is test3", "schema": {"type": "object"}}, None), + ], +) +def test_opt_from_init_validate(opt, value): + opt = base.Opt(**opt) + assert opt._loaded is False + with pytest.raises(ValidationError): + opt.load(value) + + +@pytest.mark.parametrize("opt", [{"name": "test0"}, {"help": "this is test1"}]) +def test_opt_from_init_error(opt): + with pytest.raises(TypeError): + opt = base.Opt(**opt) + + +@pytest.mark.parametrize( + "opt_schema", + [ + {"title": "test0", "description": "this is test0", "type": "string"}, + {"title": "test1", "description": "this is test1", "type": "string", "default": "test"}, + {"title": "test2", "description": "this is test2", "type": "string", "deprecated": True}, + { + "title": "test3", + "description": "this is test3", + "type": "string", + "default": "test", + "deprecated": True, + }, + ], +) +@pytest.mark.parametrize("value", ["test_value"]) +def test_opt_from_schema(opt_schema, value): + opt = base.Opt.from_schema(opt_schema) + assert opt._loaded is False + if opt.default is not None: + value = None + result = opt.default + else: + result = value + opt.load(value) + assert opt._loaded is True + assert opt.value == result + with pytest.raises(ValueError): + opt.load(value) + + +@pytest.mark.parametrize( + "opt_schema", + [ + {"title": "not_description"}, + {"description": "not title"}, + { + "title": "test_title", + "description": "deprecated is not boolean", + "deprecated": "somestring", + }, + ], +) +def test_opt_from_schema_error(opt_schema): + with pytest.raises(ValidationError): + base.Opt.from_schema(opt_schema) + + +# TODO: add test Group & Configuration + + +@pytest.mark.parametrize("config_dir", [None, "/etc/tests"]) +@pytest.mark.parametrize("config_file", [None, "/etc/tests/test.yaml"]) +def test_get_config_file(config_dir, config_file): + env = {} + if config_dir is not None: + env["OS_CONFIG_DIR"] = config_dir + if config_file is not None: + env["OS_CONFIG_FILE"] = config_file + result = [] + def_config_dir = os.path.join("/etc", "skyline") + result.append(def_config_dir if config_dir is None else config_dir) + def_config_file = os.path.join(result[0], "skyline_apiserver.yaml") + result.append(def_config_file if config_file is None else config_file) + assert base.Configuration._get_config_file(env) == tuple(result) + + +def test_get_config_file_from_env(monkeypatch): + monkeypatch.setenv("OS_CONFIG_DIR", "/etc/tests_env") + monkeypatch.setenv("OS_CONFIG_FILE", "/etc/tests_env/tests_env.yaml") + assert base.Configuration._get_config_file() == ( + "/etc/tests_env", + "/etc/tests_env/tests_env.yaml", + ) diff --git a/tests/core/config/backup_test_default.py b/tests/core/config/backup_test_default.py new file mode 100644 index 0000000..9a21b51 --- /dev/null +++ b/tests/core/config/backup_test_default.py @@ -0,0 +1,35 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from skyline_apiserver.config.default import ALL_OPTS, GROUP_NAME + + +def test_group_name(): + assert GROUP_NAME == "default" + + +@pytest.mark.parametrize("log_dir_value", [".", "./", "/", "/tmp", "/qwer"]) +@pytest.mark.parametrize("debug_value", [True, False]) +def test_all_opts(debug_value, log_dir_value): + for index, opt in enumerate(ALL_OPTS): + object.__setattr__(opt, "_loaded", False) + if index == 0: + opt.load(debug_value) + assert opt.value == debug_value + elif index == 1: + opt.load(log_dir_value) + assert opt.value == log_dir_value + object.__setattr__(opt, "_loaded", False) diff --git a/tests/core/config/backup_test_openstack.py b/tests/core/config/backup_test_openstack.py new file mode 100644 index 0000000..85d92b2 --- /dev/null +++ b/tests/core/config/backup_test_openstack.py @@ -0,0 +1,44 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from skyline_apiserver.config.openstack import ALL_OPTS, GROUP_NAME + + +def test_group_name(): + assert GROUP_NAME == "openstack" + + +@pytest.mark.parametrize("host_value", ["localhost", "127.0.0.1", "host-1", "192.168.1.1"]) +@pytest.mark.parametrize( + "keystone_url_value", + [ + "http://localhost:5000/v3/", + "http://127.0.0.1:5000/v3/", + "https://keystone:5000/v3/", + "https://keystone:5000/", + "https://keystone/", + ], +) +def test_all_opts(host_value, keystone_url_value): + for index, opt in enumerate(ALL_OPTS): + object.__setattr__(opt, "_loaded", False) + if index == 0: + opt.load(host_value) + assert opt.value == host_value + elif index == 1: + opt.load(keystone_url_value) + assert opt.value == keystone_url_value + object.__setattr__(opt, "_loaded", False) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/utils/utils.py b/tests/utils/utils.py new file mode 100644 index 0000000..71ed593 --- /dev/null +++ b/tests/utils/utils.py @@ -0,0 +1,101 @@ +# Copyright 2021 99cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import uuid + +from httpx import AsyncClient + +from skyline_apiserver import config, main, schemas +from skyline_apiserver.types import constants + + +def get_session_profile() -> schemas.Profile: + profile = schemas.Profile( + username="testUser", + keystone_token="testKeystoneToken", + region="testRegion", + project={ + "id": uuid.uuid4().hex, + "name": "testProject", + "domain": {"id": uuid.uuid4().hex, "name": "testDomain"}, + }, + user={ + "id": uuid.uuid4().hex, + "name": "testUser", + "domain": { + "id": uuid.uuid4().hex, + "name": "testDomain", + }, + }, + roles=[{"id": uuid.uuid4().hex, "name": "testRole"}], + keystone_token_exp="2221-01-13T12:29:37.000000Z", + base_roles=[], + exp=int(time.time()) + config.CONF.default.access_token_expire, + uuid=uuid.uuid4().hex, + endpoints={ + "placement": "/api/openstack/placement", + "neutron": "/api/openstack/network", + "swift": "/api/openstack/object-storage", + "nova": "/api/openstack/compute", + "heat": "/api/openstack/heat-api", + "nova_legacy": "/api/openstack/compute", + "cinderv2": "/api/openstack/volume", + "heat-cfn": "/api/openstack/heat-api-cfn", + "keystone": "/api/openstack/identity", + "cinder": "/api/openstack/volume", + "cinderv3": "/api/openstack/volume", + "glance": "/api/openstack/image", + }, + projects={ + "0e064ea01b614943993a28b2c15bd6c4": {"name": "demo", "domain_id": "default"}, + "4c017648d2e34d1a8e732b98e3232af9": {"name": "alt_demo", "domain_id": "default"}, + "e88226c062094881b7a1f01517b945b4": {"name": "admin", "domain_id": "default"}, + }, + version=constants.VERSION, + license={ + "name": "test_license_name", + "summary": "test_license_summary", + "macs": [], + "features": [{"name": "compute", "count": "3"}], + "start": "2020-12-20", + "end": "2030-10-02", + }, + currency={"zh": "元", "en": "CNY"}, + ) + return profile + + +def get_session_token() -> str: + profile = get_session_profile() + return profile.toJWTPayload() + + +async def get_jwt_from_cookie(client: AsyncClient) -> str: + login_data = { + "region": "RegionOne", + "username": config.CONF.openstack.system_user_name, + "domain": config.CONF.openstack.system_user_domain, + "password": config.CONF.openstack.system_user_password, + } + r = await client.post( + url=f"{main.API_PREFIX}/login", + json=login_data, + ) + token = r.cookies.get(config.CONF.default.session_name, "") + return token + + +async def _logout(client: AsyncClient) -> None: + await client.post(f"{main.API_PREFIX}/logout") diff --git a/tools/git_config/commit_message.txt b/tools/git_config/commit_message.txt new file mode 100644 index 0000000..31b36f6 --- /dev/null +++ b/tools/git_config/commit_message.txt @@ -0,0 +1,20 @@ +: {Commit title} + +{Commit description} + +{Commit footer} + +# --- Commit End --- +# Type can be +# feat (new feature) +# fix (bug fix) +# refactor (refactoring production code) +# style (formatting, missing semi colons, etc; no code change) +# docs (changes to documentation) +# test (adding or refactoring tests; no production code change) +# chore (updating grunt tasks etc; no production code change) +# ------------------ +# Remember to +# The title first letter should be capitalized. +# Do not end the subject line with a period. +# ------------------