feat: Add initial code of skyline-apiserver
Add initial code of skyline-apiserver Change-Id: Ib425960b707237193fd8531fb3989f29282f5b58
This commit is contained in:
parent
f18ff79249
commit
38ea9e3d59
83
.dockerignore
Normal file
83
.dockerignore
Normal file
@ -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
|
5
.flake8
Normal file
5
.flake8
Normal file
@ -0,0 +1,5 @@
|
||||
[flake8]
|
||||
max-line-length = 99
|
||||
max-doc-length = 99
|
||||
show-source = True
|
||||
extend-ignore = E203
|
77
.gitignore
vendored
Normal file
77
.gitignore
vendored
Normal file
@ -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
|
177
LICENSE
Normal file
177
LICENSE
Normal file
@ -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
|
136
Makefile
Normal file
136
Makefile
Normal file
@ -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 <TARGET>"
|
||||
@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=<some message>"$(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' {} \;
|
36
README-zh_CN.md
Normal file
36
README-zh_CN.md
Normal file
@ -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`
|
36
README.md
Normal file
36
README.md
Normal file
@ -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`
|
37
alembic.ini
Normal file
37
alembic.ini
Normal file
@ -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 =
|
2979
docs/api/swagger.json
Normal file
2979
docs/api/swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
2
etc/.gitignore
vendored
Normal file
2
etc/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.yaml
|
||||
proxy.conf
|
8
etc/gunicorn.py
Normal file
8
etc/gunicorn.py
Normal file
@ -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"
|
5
libs/skyline-log/.flake8
Normal file
5
libs/skyline-log/.flake8
Normal file
@ -0,0 +1,5 @@
|
||||
[flake8]
|
||||
max-line-length = 99
|
||||
max-doc-length = 99
|
||||
show-source = True
|
||||
extend-ignore = E203
|
177
libs/skyline-log/LICENSE
Normal file
177
libs/skyline-log/LICENSE
Normal file
@ -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
|
35
libs/skyline-log/Makefile
Normal file
35
libs/skyline-log/Makefile
Normal file
@ -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'`
|
3
libs/skyline-log/mypy.ini
Normal file
3
libs/skyline-log/mypy.ini
Normal file
@ -0,0 +1,3 @@
|
||||
[mypy]
|
||||
show_error_codes = true
|
||||
show_error_context = true
|
502
libs/skyline-log/poetry.lock
generated
Normal file
502
libs/skyline-log/poetry.lock
generated
Normal file
@ -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"},
|
||||
]
|
2
libs/skyline-log/poetry.toml
Normal file
2
libs/skyline-log/poetry.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[virtualenvs]
|
||||
in-project = true
|
54
libs/skyline-log/pyproject.toml
Normal file
54
libs/skyline-log/pyproject.toml
Normal file
@ -0,0 +1,54 @@
|
||||
[tool.poetry]
|
||||
name = "skyline-log"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
license = "Apache-2.0"
|
||||
authors = ["OpenStack <openstack-discuss@lists.openstack.org>"]
|
||||
|
||||
[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"
|
19
libs/skyline-log/src/skyline_log/__init__.py
Normal file
19
libs/skyline-log/src/skyline_log/__init__.py
Normal file
@ -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")
|
82
libs/skyline-log/src/skyline_log/log.py
Normal file
82
libs/skyline-log/src/skyline_log/log.py
Normal file
@ -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=(
|
||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> |"
|
||||
" <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> -"
|
||||
" <level>{message}</level>"
|
||||
),
|
||||
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")
|
0
libs/skyline-log/tests/__init__.py
Normal file
0
libs/skyline-log/tests/__init__.py
Normal file
0
libs/skyline-log/tests/test_log.py
Normal file
0
libs/skyline-log/tests/test_log.py
Normal file
19
libs/skyline-log/tests/test_skyline_log.py
Normal file
19
libs/skyline-log/tests/test_skyline_log.py
Normal file
@ -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"
|
5
libs/skyline-policy-manager/.flake8
Normal file
5
libs/skyline-policy-manager/.flake8
Normal file
@ -0,0 +1,5 @@
|
||||
[flake8]
|
||||
max-line-length = 99
|
||||
max-doc-length = 99
|
||||
show-source = True
|
||||
extend-ignore = E203
|
177
libs/skyline-policy-manager/LICENSE
Normal file
177
libs/skyline-policy-manager/LICENSE
Normal file
@ -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
|
37
libs/skyline-policy-manager/Makefile
Normal file
37
libs/skyline-policy-manager/Makefile
Normal file
@ -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'`
|
4
libs/skyline-policy-manager/mypy.ini
Normal file
4
libs/skyline-policy-manager/mypy.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[mypy]
|
||||
show_error_codes = true
|
||||
show_error_context = true
|
||||
mypy_path = ../skyline-log/src
|
2269
libs/skyline-policy-manager/poetry.lock
generated
Normal file
2269
libs/skyline-policy-manager/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
libs/skyline-policy-manager/poetry.toml
Normal file
2
libs/skyline-policy-manager/poetry.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[virtualenvs]
|
||||
in-project = true
|
64
libs/skyline-policy-manager/pyproject.toml
Normal file
64
libs/skyline-policy-manager/pyproject.toml
Normal file
@ -0,0 +1,64 @@
|
||||
[tool.poetry]
|
||||
name = "skyline-policy-manager"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
license = "Apache-2.0"
|
||||
authors = ["OpenStack <openstack-discuss@lists.openstack.org>"]
|
||||
|
||||
[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"
|
@ -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"
|
@ -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)
|
@ -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: [<entry_point_name>, <entry_point_name>,]
|
||||
"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()
|
@ -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",)
|
@ -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,
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -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",)
|
@ -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",)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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",)
|
@ -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",)
|
@ -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",)
|
@ -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")
|
0
libs/skyline-policy-manager/tests/__init__.py
Normal file
0
libs/skyline-policy-manager/tests/__init__.py
Normal file
@ -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"
|
30
libs/skyline-policy-manager/tools/post_install.sh
Executable file
30
libs/skyline-policy-manager/tools/post_install.sh
Executable file
@ -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
|
37
mypy.ini
Normal file
37
mypy.ini
Normal file
@ -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
|
3087
poetry.lock
generated
Normal file
3087
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
poetry.toml
Normal file
2
poetry.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[virtualenvs]
|
||||
in-project = true
|
97
pyproject.toml
Normal file
97
pyproject.toml
Normal file
@ -0,0 +1,97 @@
|
||||
[tool.poetry]
|
||||
name = "skyline-apiserver"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
license = "Apache-2.0"
|
||||
authors = ["OpenStack <openstack-discuss@lists.openstack.org>"]
|
||||
|
||||
[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"
|
15
src/skyline_apiserver/__init__.py
Normal file
15
src/skyline_apiserver/__init__.py
Normal file
@ -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"
|
34
src/skyline_apiserver/__main__.py
Normal file
34
src/skyline_apiserver/__main__.py
Normal file
@ -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())
|
0
src/skyline_apiserver/api/__init__.py
Normal file
0
src/skyline_apiserver/api/__init__.py
Normal file
83
src/skyline_apiserver/api/deps.py
Normal file
83
src/skyline_apiserver/api/deps.py
Normal file
@ -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
|
24
src/skyline_apiserver/api/v1/__init__.py
Normal file
24
src/skyline_apiserver/api/v1/__init__.py
Normal file
@ -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"])
|
112
src/skyline_apiserver/api/v1/contrib.py
Normal file
112
src/skyline_apiserver/api/v1/contrib.py
Normal file
@ -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),
|
||||
)
|
1052
src/skyline_apiserver/api/v1/extension.py
Normal file
1052
src/skyline_apiserver/api/v1/extension.py
Normal file
File diff suppressed because it is too large
Load Diff
190
src/skyline_apiserver/api/v1/login.py
Normal file
190
src/skyline_apiserver/api/v1/login.py
Normal file
@ -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
|
0
src/skyline_apiserver/api/v1/openstack/__init__.py
Normal file
0
src/skyline_apiserver/api/v1/openstack/__init__.py
Normal file
240
src/skyline_apiserver/api/v1/openstack/base.py
Normal file
240
src/skyline_apiserver/api/v1/openstack/base.py
Normal file
@ -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)
|
92
src/skyline_apiserver/api/v1/policy.py
Normal file
92
src/skyline_apiserver/api/v1/policy.py
Normal file
@ -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}
|
140
src/skyline_apiserver/api/v1/setting.py
Normal file
140
src/skyline_apiserver/api/v1/setting.py
Normal file
@ -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)
|
263
src/skyline_apiserver/api/v1/utils.py
Normal file
263
src/skyline_apiserver/api/v1/utils.py
Normal file
@ -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
|
0
src/skyline_apiserver/client/__init__.py
Normal file
0
src/skyline_apiserver/client/__init__.py
Normal file
0
src/skyline_apiserver/client/openstack/__init__.py
Normal file
0
src/skyline_apiserver/client/openstack/__init__.py
Normal file
83
src/skyline_apiserver/client/openstack/cinder.py
Normal file
83
src/skyline_apiserver/client/openstack/cinder.py
Normal file
@ -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),
|
||||
)
|
48
src/skyline_apiserver/client/openstack/glance.py
Normal file
48
src/skyline_apiserver/client/openstack/glance.py
Normal file
@ -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),
|
||||
)
|
73
src/skyline_apiserver/client/openstack/keystone.py
Normal file
73
src/skyline_apiserver/client/openstack/keystone.py
Normal file
@ -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),
|
||||
)
|
41
src/skyline_apiserver/client/openstack/neutron.py
Normal file
41
src/skyline_apiserver/client/openstack/neutron.py
Normal file
@ -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),
|
||||
)
|
86
src/skyline_apiserver/client/openstack/nova.py
Normal file
86
src/skyline_apiserver/client/openstack/nova.py
Normal file
@ -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),
|
||||
)
|
92
src/skyline_apiserver/client/openstack/system.py
Normal file
92
src/skyline_apiserver/client/openstack/system.py
Normal file
@ -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
|
151
src/skyline_apiserver/client/utils.py
Normal file
151
src/skyline_apiserver/client/utils.py
Normal file
@ -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
|
0
src/skyline_apiserver/cmd/__init__.py
Normal file
0
src/skyline_apiserver/cmd/__init__.py
Normal file
61
src/skyline_apiserver/cmd/generate_swagger.py
Normal file
61
src/skyline_apiserver/cmd/generate_swagger.py
Normal file
@ -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()
|
38
src/skyline_apiserver/config/__init__.py
Normal file
38
src/skyline_apiserver/config/__init__.py
Normal file
@ -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")
|
152
src/skyline_apiserver/config/base.py
Normal file
152
src/skyline_apiserver/config/base.py
Normal file
@ -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")
|
91
src/skyline_apiserver/config/default.py
Normal file
91
src/skyline_apiserver/config/default.py
Normal file
@ -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")
|
31
src/skyline_apiserver/config/developer.py
Normal file
31
src/skyline_apiserver/config/developer.py
Normal file
@ -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")
|
218
src/skyline_apiserver/config/openstack.py
Normal file
218
src/skyline_apiserver/config/openstack.py
Normal file
@ -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 <service_type>:<service_name>"
|
||||
),
|
||||
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")
|
113
src/skyline_apiserver/config/setting.py
Normal file
113
src/skyline_apiserver/config/setting.py
Normal file
@ -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")
|
0
src/skyline_apiserver/core/__init__.py
Normal file
0
src/skyline_apiserver/core/__init__.py
Normal file
142
src/skyline_apiserver/core/security.py
Normal file
142
src/skyline_apiserver/core/security.py
Normal file
@ -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,
|
||||
)
|
18
src/skyline_apiserver/db/__init__.py
Normal file
18
src/skyline_apiserver/db/__init__.py
Normal file
@ -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")
|
1
src/skyline_apiserver/db/alembic/README
Normal file
1
src/skyline_apiserver/db/alembic/README
Normal file
@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
0
src/skyline_apiserver/db/alembic/__init__.py
Normal file
0
src/skyline_apiserver/db/alembic/__init__.py
Normal file
81
src/skyline_apiserver/db/alembic/env.py
Normal file
81
src/skyline_apiserver/db/alembic/env.py
Normal file
@ -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()
|
24
src/skyline_apiserver/db/alembic/script.py.mako
Normal file
24
src/skyline_apiserver/db/alembic/script.py.mako
Normal file
@ -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"}
|
55
src/skyline_apiserver/db/alembic/versions/000_init.py
Normal file
55
src/skyline_apiserver/db/alembic/versions/000_init.py
Normal file
@ -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 ###
|
123
src/skyline_apiserver/db/api.py
Normal file
123
src/skyline_apiserver/db/api.py
Normal file
@ -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
|
53
src/skyline_apiserver/db/base.py
Normal file
53
src/skyline_apiserver/db/base.py
Normal file
@ -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)
|
34
src/skyline_apiserver/db/models.py
Normal file
34
src/skyline_apiserver/db/models.py
Normal file
@ -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),
|
||||
)
|
61
src/skyline_apiserver/main.py
Normal file
61
src/skyline_apiserver/main.py
Normal file
@ -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)
|
0
src/skyline_apiserver/network/__init__.py
Normal file
0
src/skyline_apiserver/network/__init__.py
Normal file
53
src/skyline_apiserver/network/neutron.py
Normal file
53
src/skyline_apiserver/network/neutron.py
Normal file
@ -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()
|
39
src/skyline_apiserver/policies/__init__.py
Normal file
39
src/skyline_apiserver/policies/__init__.py
Normal file
@ -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",
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user