feat: Add initial code of skyline-apiserver

Add initial code of skyline-apiserver

Change-Id: Ib425960b707237193fd8531fb3989f29282f5b58
This commit is contained in:
Hanxiang Gao 2021-06-01 23:38:51 +08:00
parent f18ff79249
commit 38ea9e3d59
130 changed files with 27975 additions and 0 deletions

83
.dockerignore Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

2
etc/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.yaml
proxy.conf

8
etc/gunicorn.py Normal file
View 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
View 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
View 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
View 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'`

View File

@ -0,0 +1,3 @@
[mypy]
show_error_codes = true
show_error_context = true

502
libs/skyline-log/poetry.lock generated Normal file
View 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"},
]

View File

@ -0,0 +1,2 @@
[virtualenvs]
in-project = true

View 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"

View 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")

View 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")

View File

View File

View 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"

View File

@ -0,0 +1,5 @@
[flake8]
max-line-length = 99
max-doc-length = 99
show-source = True
extend-ignore = E203

View 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

View 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'`

View 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
[virtualenvs]
in-project = true

View 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"

View 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"

View File

@ -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)

View File

@ -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()

View 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 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",)

View File

@ -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

View File

@ -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",)

View File

@ -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

View File

@ -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",)

View File

@ -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",)

View File

@ -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",)

View File

@ -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")

View 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"

View 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
View 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

File diff suppressed because it is too large Load Diff

2
poetry.toml Normal file
View File

@ -0,0 +1,2 @@
[virtualenvs]
in-project = true

97
pyproject.toml Normal file
View 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"

View 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"

View 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())

View File

View 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

View 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"])

View 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),
)

File diff suppressed because it is too large Load Diff

View 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

View 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)

View 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}

View 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)

View 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

View File

View 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),
)

View 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),
)

View 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),
)

View 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),
)

View 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),
)

View 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

View 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

View File

View 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()

View 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")

View 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")

View 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")

View 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")

View 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")

View 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")

View File

View 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,
)

View 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")

View File

@ -0,0 +1 @@
Generic single-database configuration.

View 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()

View 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"}

View 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 ###

View 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

View 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)

View 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),
)

View 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)

View 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()

View 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