feat: Add skyline-nginx package
1. Add skyline-nginx package for managing and generating nginx configuration. Change-Id: I68afd9ab9ad52fc96c13e745cb1e89a8061a53ba
This commit is contained in:
parent
dfaf962aca
commit
5e4f40f364
41
libs/skyline-nginx/Makefile
Normal file
41
libs/skyline-nginx/Makefile
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
PYTHON ?= python3
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: install fmt lint test package
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: venv
|
||||||
|
venv:
|
||||||
|
poetry env use $(PYTHON)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install: venv
|
||||||
|
poetry run pip install -U pip setuptools
|
||||||
|
poetry install -vvv
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: package
|
||||||
|
package:
|
||||||
|
poetry build -f wheel
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt:
|
||||||
|
poetry run isort $$(git ls-files -- **/*.py)
|
||||||
|
poetry run black --config ../../pyproject.toml $$(git ls-files -- **/*.py)
|
||||||
|
poetry run add-trailing-comma --py36-plus --exit-zero-even-if-changed $$(git ls-files -- **/*.py)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
poetry run mypy --no-incremental $$(git ls-files -- **/*.py)
|
||||||
|
poetry run isort --check-only --diff $$(git ls-files -- **/*.py)
|
||||||
|
poetry run black --check --diff --color --config ../../pyproject.toml $$(git ls-files -- **/*.py)
|
||||||
|
poetry run flake8 $$(git ls-files -- **/*.py)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
echo TODO
|
2939
libs/skyline-nginx/poetry.lock
generated
Normal file
2939
libs/skyline-nginx/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
libs/skyline-nginx/poetry.toml
Normal file
2
libs/skyline-nginx/poetry.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[virtualenvs]
|
||||||
|
in-project = true
|
38
libs/skyline-nginx/pyproject.toml
Normal file
38
libs/skyline-nginx/pyproject.toml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "skyline-nginx"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
license = "Apache-2.0"
|
||||||
|
authors = ["OpenStack <openstack-discuss@lists.openstack.org>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.8"
|
||||||
|
pydantic = "*"
|
||||||
|
click = "*"
|
||||||
|
jinja2 = "*"
|
||||||
|
python-keystoneclient = "*"
|
||||||
|
keystoneauth1 = "*"
|
||||||
|
skyline-config = "*"
|
||||||
|
skyline-log = "*"
|
||||||
|
skyline-console = "*"
|
||||||
|
skyline-apiserver = "*"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
isort = "*"
|
||||||
|
black = "^21.5b1"
|
||||||
|
add-trailing-comma = "*"
|
||||||
|
flake8 = "*"
|
||||||
|
mypy = "*"
|
||||||
|
pytest = "*"
|
||||||
|
pytest-xdist = {extras = ["psutil"], version = "*"}
|
||||||
|
skyline-config = {path = "../skyline-config", develop = true}
|
||||||
|
skyline-log = {path = "../skyline-log", develop = true}
|
||||||
|
skyline-console = {path = "../skyline-console", develop = true}
|
||||||
|
skyline-apiserver = {path = "../skyline-apiserver", develop = true}
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
nginx-generator = 'skyline_nginx.cmd.generate_nginx:main'
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
15
libs/skyline-nginx/skyline_nginx/__init__.py
Normal file
15
libs/skyline-nginx/skyline_nginx/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2021 99cloud
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
0
libs/skyline-nginx/skyline_nginx/cmd/__init__.py
Normal file
0
libs/skyline-nginx/skyline_nginx/cmd/__init__.py
Normal file
166
libs/skyline-nginx/skyline_nginx/cmd/generate_nginx.py
Normal file
166
libs/skyline-nginx/skyline_nginx/cmd/generate_nginx.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# 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 sys
|
||||||
|
from logging import StreamHandler
|
||||||
|
from pathlib import Path, PurePath
|
||||||
|
from typing import Any, Dict
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import click
|
||||||
|
import skyline_nginx
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from jinja2 import Template
|
||||||
|
from keystoneauth1.identity.v3 import Password, Token
|
||||||
|
from keystoneauth1.session import Session
|
||||||
|
from keystoneclient.client import Client as KeystoneClient
|
||||||
|
from skyline_console import static_path
|
||||||
|
from skyline_log import LOG, setup
|
||||||
|
from skyline_nginx.config import CONF, configure
|
||||||
|
|
||||||
|
class CommandException(Exception):
|
||||||
|
EXIT_CODE = 1
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyEndpoint(BaseModel):
|
||||||
|
part: str
|
||||||
|
location: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_session() -> 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,
|
||||||
|
)
|
||||||
|
return Session(auth=auth, verify=False, timeout=30)
|
||||||
|
|
||||||
|
|
||||||
|
def get_proxy_endpoints() -> Dict[str, ProxyEndpoint]:
|
||||||
|
ks_client = KeystoneClient(
|
||||||
|
session=get_system_session(),
|
||||||
|
interface=CONF.openstack.interface_type,
|
||||||
|
region_name=CONF.openstack.default_region,
|
||||||
|
)
|
||||||
|
endpoints_list = ks_client.endpoints.list(interface=CONF.openstack.interface_type)
|
||||||
|
service_list = ks_client.services.list()
|
||||||
|
services = {s.id: s.type for s in service_list}
|
||||||
|
|
||||||
|
endpoints = {}
|
||||||
|
for endpoint in endpoints_list:
|
||||||
|
proxy = ProxyEndpoint(part="", location="", url="")
|
||||||
|
region = endpoint.region
|
||||||
|
service_type = services.get(endpoint.service_id)
|
||||||
|
service = CONF.openstack.service_mapping.get(service_type)
|
||||||
|
if service is None:
|
||||||
|
continue
|
||||||
|
if f"{region}-{service_type}" in endpoints:
|
||||||
|
raise KeyError(f"Region \"{region}\" service type \"{service_type}\" conflict in endpoints.")
|
||||||
|
|
||||||
|
proxy.part = f"# {region} {service}"
|
||||||
|
location = PurePath("/").joinpath(
|
||||||
|
CONF.openstack.nginx_prefix,
|
||||||
|
region.lower(),
|
||||||
|
service,
|
||||||
|
)
|
||||||
|
proxy.location = f"{str(location)}/"
|
||||||
|
|
||||||
|
raw_url = urlparse(endpoint.url)
|
||||||
|
path = ""
|
||||||
|
if raw_url.path:
|
||||||
|
raw_path = PurePath(raw_url.path)
|
||||||
|
if len(raw_path.parts) > 1:
|
||||||
|
if raw_path.match("%(tenant_id)s") or raw_path.match("$(project_id)s"):
|
||||||
|
path = (
|
||||||
|
"" if str(raw_path.parents[1]) == "/" else raw_path.parents[1]
|
||||||
|
)
|
||||||
|
elif raw_path.match("v[0-9]") or raw_path.match("v[0-9][.][0-9]"):
|
||||||
|
path = (
|
||||||
|
"" if str(raw_path.parents[0]) == "/" else raw_path.parents[0]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
path = raw_path
|
||||||
|
|
||||||
|
proxy.url = raw_url._replace(path=f"{str(path)}/").geturl()
|
||||||
|
endpoints[f"{region}-{service_type}"] = proxy
|
||||||
|
|
||||||
|
return dict(sorted(endpoints.items(), key=lambda d: d[0]))
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(help="Generate nginx proxy config file.")
|
||||||
|
@click.option(
|
||||||
|
"-o",
|
||||||
|
"--output-file",
|
||||||
|
"output_file_path",
|
||||||
|
help=(
|
||||||
|
"The path of the output file, this file is to generate a reverse proxy configuration "
|
||||||
|
"file based on the openstack endpoint and should be used in the location part of nginx."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ssl-certfile",
|
||||||
|
"ssl_certfile",
|
||||||
|
help=("SSL certificate file path."),
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--ssl-keyfile",
|
||||||
|
"ssl_keyfile",
|
||||||
|
help=("SSL key file path."),
|
||||||
|
)
|
||||||
|
def main(output_file_path: str, ssl_certfile: str, ssl_keyfile: str) -> None:
|
||||||
|
try:
|
||||||
|
configure("skyline")
|
||||||
|
setup(StreamHandler(), debug=CONF.default.debug)
|
||||||
|
|
||||||
|
template_file_path = (
|
||||||
|
Path(skyline_nginx.__file__).parent
|
||||||
|
.joinpath("templates")
|
||||||
|
.joinpath("nginx.conf.j2")
|
||||||
|
)
|
||||||
|
content = ""
|
||||||
|
with template_file_path.open() as f:
|
||||||
|
content = f.read()
|
||||||
|
template = Template(content)
|
||||||
|
|
||||||
|
endpoints = get_proxy_endpoints()
|
||||||
|
context = {
|
||||||
|
"skyline_console_static_path": static_path,
|
||||||
|
"endpoints": [i.dict() for i in endpoints.values()],
|
||||||
|
}
|
||||||
|
if ssl_certfile:
|
||||||
|
context.update(ssl_certfile=ssl_certfile)
|
||||||
|
if ssl_keyfile:
|
||||||
|
context.update(ssl_certfile=ssl_keyfile)
|
||||||
|
result = template.render(**context)
|
||||||
|
|
||||||
|
if output_file_path:
|
||||||
|
with open(output_file_path, mode="w") as f:
|
||||||
|
f.write(result)
|
||||||
|
else:
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
except CommandException as e:
|
||||||
|
LOG.error(e)
|
||||||
|
sys.exit(e.EXIT_CODE)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
37
libs/skyline-nginx/skyline_nginx/config/__init__.py
Normal file
37
libs/skyline-nginx/skyline_nginx/config/__init__.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# 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 skyline_config import Configuration, Group
|
||||||
|
|
||||||
|
from . import default, openstack
|
||||||
|
|
||||||
|
CONF = Configuration()
|
||||||
|
|
||||||
|
|
||||||
|
def configure(project: str, setup: bool = True) -> None:
|
||||||
|
conf_modules = (
|
||||||
|
(default.GROUP_NAME, default.ALL_OPTS),
|
||||||
|
(openstack.GROUP_NAME, openstack.ALL_OPTS),
|
||||||
|
)
|
||||||
|
groups = [Group(*item) for item in conf_modules]
|
||||||
|
CONF(groups)
|
||||||
|
if setup:
|
||||||
|
CONF.setup(project, os.environ.copy())
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ("CONF", "configure")
|
22
libs/skyline-nginx/skyline_nginx/config/default.py
Normal file
22
libs/skyline-nginx/skyline_nginx/config/default.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Copyright 2021 99cloud
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from skyline_apiserver.config.default import debug
|
||||||
|
|
||||||
|
GROUP_NAME = __name__.split(".")[-1]
|
||||||
|
ALL_OPTS = (debug,)
|
||||||
|
|
||||||
|
__all__ = ("GROUP_NAME", "ALL_OPTS")
|
44
libs/skyline-nginx/skyline_nginx/config/openstack.py
Normal file
44
libs/skyline-nginx/skyline_nginx/config/openstack.py
Normal 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.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from skyline_apiserver.config.openstack import (
|
||||||
|
default_region,
|
||||||
|
interface_type,
|
||||||
|
keystone_url,
|
||||||
|
nginx_prefix,
|
||||||
|
service_mapping,
|
||||||
|
system_project,
|
||||||
|
system_project_domain,
|
||||||
|
system_user_domain,
|
||||||
|
system_user_name,
|
||||||
|
system_user_password,
|
||||||
|
)
|
||||||
|
|
||||||
|
GROUP_NAME = __name__.split(".")[-1]
|
||||||
|
ALL_OPTS = (
|
||||||
|
default_region,
|
||||||
|
keystone_url,
|
||||||
|
system_project_domain,
|
||||||
|
system_project,
|
||||||
|
system_user_domain,
|
||||||
|
system_user_name,
|
||||||
|
system_user_password,
|
||||||
|
interface_type,
|
||||||
|
nginx_prefix,
|
||||||
|
service_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ("GROUP_NAME", "ALL_OPTS")
|
0
libs/skyline-nginx/skyline_nginx/py.typed
Normal file
0
libs/skyline-nginx/skyline_nginx/py.typed
Normal file
114
libs/skyline-nginx/skyline_nginx/templates/nginx.conf.j2
Normal file
114
libs/skyline-nginx/skyline_nginx/templates/nginx.conf.j2
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
worker_processes auto;
|
||||||
|
pid /run/nginx.pid;
|
||||||
|
include /etc/nginx/modules-enabled/*.conf;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
multi_accept on;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
##
|
||||||
|
# Basic Settings
|
||||||
|
##
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
client_max_body_size 0;
|
||||||
|
types_hash_max_size 2048;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# server_names_hash_bucket_size 64;
|
||||||
|
# server_name_in_redirect off;
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
##
|
||||||
|
# SSL Settings
|
||||||
|
##
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
# Self signed certs generated by the ssl-cert package
|
||||||
|
# Don't use them in a production server!
|
||||||
|
ssl_certificate {{ ssl_certfile | default('/etc/ssl/certs/ssl-cert-snakeoil.pem') }};
|
||||||
|
ssl_certificate_key {{ ssl_keyfile | default('/etc/ssl/private/ssl-cert-snakeoil.key') }};
|
||||||
|
|
||||||
|
##
|
||||||
|
# Logging Settings
|
||||||
|
##
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request_time" '
|
||||||
|
'"$upstream_response_time" "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
access_log /var/log/skyline/nginx_access.log main;
|
||||||
|
error_log /var/log/skyline/nginx_error.log;
|
||||||
|
|
||||||
|
##
|
||||||
|
# Gzip Settings
|
||||||
|
##
|
||||||
|
gzip on;
|
||||||
|
gzip_static on;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
# gzip_http_version 1.1;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
upstream skyline {
|
||||||
|
server unix:/var/lib/skyline/skyline.sock fail_timeout=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Virtual Host Configs
|
||||||
|
##
|
||||||
|
server {
|
||||||
|
listen 8080 ssl http2 default_server;
|
||||||
|
|
||||||
|
root {{ skyline_console_static_path }};
|
||||||
|
|
||||||
|
# Add index.php to the list if you are using PHP
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# First attempt to serve request as file, then
|
||||||
|
# as directory, then fall back to displaying a 404.
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/openstack/skyline/ {
|
||||||
|
proxy_pass http://skyline/;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for endpoint in endpoints %}
|
||||||
|
{{ endpoint["part"] }}
|
||||||
|
location {{ endpoint["location"] }} {
|
||||||
|
proxy_pass {{ endpoint["url"] }};
|
||||||
|
proxy_redirect {{ endpoint["url"] }} {{ endpoint["location"] }};
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
0
libs/skyline-nginx/tests/__init__.py
Normal file
0
libs/skyline-nginx/tests/__init__.py
Normal file
19
libs/skyline-nginx/tests/test_skyline_nginx.py
Normal file
19
libs/skyline-nginx/tests/test_skyline_nginx.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Copyright 2021 99cloud
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from skyline_log import __version__
|
||||||
|
|
||||||
|
|
||||||
|
def test_version():
|
||||||
|
assert __version__ == "0.1.0"
|
Loading…
Reference in New Issue
Block a user