Refactoring
- enable public/private routes exectuion - implementing new DB model - routes - refactoring service model to allow sub-apps - examples/hello-lambda.sh was changed to reflect new routes API
This commit is contained in:
parent
4471a4081a
commit
68b54f28bb
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ python-troveclient.iml
|
|||||||
# Files created by releasenotes build
|
# Files created by releasenotes build
|
||||||
releasenotes/build
|
releasenotes/build
|
||||||
.coverage.*
|
.coverage.*
|
||||||
|
*.json
|
||||||
|
@ -123,12 +123,14 @@ In [examples](examples/) folder you can find a script that examines available AP
|
|||||||
* `LAOS_API_URL` - Project LaOS API endpoint
|
* `LAOS_API_URL` - Project LaOS API endpoint
|
||||||
* `OS_AUTH_URL` - OpenStack Auth URL
|
* `OS_AUTH_URL` - OpenStack Auth URL
|
||||||
* `OS_PROJECT_ID` - it can be found in OpenStack Dashboard or in CLI
|
* `OS_PROJECT_ID` - it can be found in OpenStack Dashboard or in CLI
|
||||||
|
* `OS_USERNAME` - OpenStack project-aligned username
|
||||||
Along with that, you need to adjust [token_request.json](examples/token_request.json) in order to retrieve X-Auth-Token for further authentication against OpenStack and LaOS API service.
|
* `OS_PASSWORD` - OpenStack project-aligned user password
|
||||||
|
* `OS_DOMAIN` - OpenStack project domain name
|
||||||
|
* `OS_PROJECT_NAME` - OpenStack project name
|
||||||
|
|
||||||
Then just run script:
|
Then just run script:
|
||||||
|
|
||||||
OS_AUTH_URL=http://192.168.0.112:5000/v3 OS_PROJECT_ID=8fb76785313a4500ac5367eb44a31677 ./hello-lambda.sh
|
OS_AUTH_URL=http://192.168.0.112:5000/v3 OS_PROJECT_ID=8fb76785313a4500ac5367eb44a31677 OS_USERNAME=admin OS_PASSWORD=root OS_DOMAIN=default OS_PROJECT_NAME=admin ./examples/hello-lambda.sh
|
||||||
|
|
||||||
Please note, that given values are project-specific, so they can't be reused.
|
Please note, that given values are project-specific, so they can't be reused.
|
||||||
|
|
||||||
|
@ -1,18 +1,47 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set +x
|
set +x
|
||||||
|
set +e
|
||||||
|
|
||||||
export LAOS_API_URL=${LAOS_API_URL:-http://localhost:10001}
|
export LAOS_API_URL=${LAOS_API_URL:-http://localhost:10001}
|
||||||
|
|
||||||
export OS_AUTH_URL=${OS_AUTH_URL:-http://localhost:5000/v3}
|
export OS_AUTH_URL=${OS_AUTH_URL:-http://localhost:5000/v3}
|
||||||
|
export OS_USERNAME=${OS_USERNAME:-admin}
|
||||||
|
export OS_PASSOWRD=${OS_PASSWORD:-root}
|
||||||
|
export OS_DOMAIN=${OS_DOMAIN:-default}
|
||||||
export OS_PROJECT_ID=${OS_PROJECT_ID:-"dummy_project_id"}
|
export OS_PROJECT_ID=${OS_PROJECT_ID:-"dummy_project_id"}
|
||||||
export OS_TOKEN=`curl -si -d @token_request.json -H "Content-type: application/json" ${OS_AUTH_URL}/auth/tokens | awk '/X-Subject-Token/ {print $2}'`
|
|
||||||
|
|
||||||
|
rm -fr examples/token_request.json
|
||||||
|
echo -e "{
|
||||||
|
\"auth\": {
|
||||||
|
\"identity\": {
|
||||||
|
\"methods\": [\"password\"],
|
||||||
|
\"password\": {
|
||||||
|
\"user\": {
|
||||||
|
\"name\": \"${OS_USERNAME:-admin}\",
|
||||||
|
\"domain\": { \"id\": \"${OS_DOMAIN:-default}\" },
|
||||||
|
\"password\": \"${OS_PASSWORD:-root}\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
\"scope\": {
|
||||||
|
\"project\": {
|
||||||
|
\"name\": \"${OS_PROJECT_NAME:-admin}\",
|
||||||
|
\"domain\": {\"id\": \"${OS_DOMAIN:-default}\" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}" >> examples/token_request.json
|
||||||
|
|
||||||
|
|
||||||
|
export OS_TOKEN=`curl -si -d @examples/token_request.json -H "Content-type: application/json" ${OS_AUTH_URL}/auth/tokens | awk '/X-Subject-Token/ {print $2}'`
|
||||||
|
|
||||||
echo -e "Listing apps\n"
|
echo -e "Listing apps\n"
|
||||||
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Creating app\n"
|
echo -e "Creating app\n"
|
||||||
curl -X POST -d '{"app":{"name": "testapp"}}' ${LAOS_API_URL}/v1/${PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl -X POST -d '{"app":{"name": "testapp"}}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Listing apps\n"
|
echo -e "Listing apps\n"
|
||||||
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
@ -20,32 +49,48 @@ curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "
|
|||||||
echo -e "Showing app info\n"
|
echo -e "Showing app info\n"
|
||||||
export raw_app_info=`curl localhost:10001/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool | grep name | awk '{print $2}'`
|
export raw_app_info=`curl localhost:10001/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool | grep name | awk '{print $2}'`
|
||||||
export app_name=${raw_app_info:1:30}
|
export app_name=${raw_app_info:1:30}
|
||||||
unset $raw_app_info
|
|
||||||
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Listing app routes\n"
|
echo -e "Listing app routes\n"
|
||||||
curl ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Creating app sync route\n"
|
echo -e "Creating app sync private route\n"
|
||||||
curl -X POST -d '{"route":{"type": "sync", "path": "/hello-sync", "image": "iron/hello"}}' ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/route -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json"
|
curl -X POST -d '{"route":{"type": "sync", "path": "/hello-sync-private", "image": "iron/hello", "is_public": "false" }}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
|
echo -e "Creating app sync public route\n"
|
||||||
|
curl -X POST -d '{"route":{"type": "sync", "path": "/hello-sync-public", "image": "iron/hello", "is_public": "true" }}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Listing app routes\n"
|
echo -e "Listing app routes\n"
|
||||||
curl ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Show app route\n"
|
echo -e "Show app private route\n"
|
||||||
curl ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes/hello-sync -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Running app sync route\n"
|
echo -e "Show app public route\n"
|
||||||
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/r/${PROJECT_ID}/${APP_NAME}/hello-sync -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-public -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Deleting app route\n"
|
echo -e "Running app sync private route\n"
|
||||||
curl -X DELETE ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/routes/hello_sync -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/private/${OS_PROJECT_ID}/${app_name}/hello-sync-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
|
echo -e "Running app sync public route\n"
|
||||||
|
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/public/${app_name}/hello-sync-public -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Creating app async route\n"
|
echo -e "Creating app async route\n"
|
||||||
curl -X POST -d '{"route":{"type": "async", "path": "/hello-async", "image": "iron/hello"}}' ${LAOS_API_URL}/v1/${PROJECT_ID}/apps/${APP_NAME}/route -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl -X POST -d '{"route":{"type": "async", "path": "/hello-async-private", "image": "iron/hello", "is_public": "false"}}' ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Running app async route\n"
|
echo -e "Running app async route\n"
|
||||||
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/r/${PROJECT_ID}/${APP_NAME}/hello-async -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl -X POST -d '{"name": "Johnny"}' ${LAOS_API_URL}/private/${OS_PROJECT_ID}/${app_name}/hello-async-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
|
echo -e "Deleting app route\n"
|
||||||
|
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-public -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-sync-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes/hello-async-private -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
|
echo -e "Listing app routes\n"
|
||||||
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name}/routes -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
echo -e "Deleting app\n"
|
echo -e "Deleting app\n"
|
||||||
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
curl -X DELETE ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps/${app_name} -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
|
||||||
|
echo -e "Listing apps\n"
|
||||||
|
curl ${LAOS_API_URL}/v1/${OS_PROJECT_ID}/apps -H "X-Auth-Token:${OS_TOKEN}" -H "Content-Type: application/json" | python3 -mjson.tool
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"auth": {
|
|
||||||
"identity": {
|
|
||||||
"methods": ["password"],
|
|
||||||
"password": {
|
|
||||||
"user": {
|
|
||||||
"name": "admin",
|
|
||||||
"domain": { "id": "default" },
|
|
||||||
"password": "root"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scope": {
|
|
||||||
"project": {
|
|
||||||
"name": "admin",
|
|
||||||
"domain": { "id": "default" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,6 +12,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from laos.api.views import app as app_view
|
from laos.api.views import app as app_view
|
||||||
@ -34,6 +36,10 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
log, fnclient = c.logger, c.functions_client
|
log, fnclient = c.logger, c.functions_client
|
||||||
project_id = request.match_info.get('project_id')
|
project_id = request.match_info.get('project_id')
|
||||||
app = request.match_info.get('app')
|
app = request.match_info.get('app')
|
||||||
|
|
||||||
|
log.info("Listing app {} routes for project: {}."
|
||||||
|
.format(app, project_id))
|
||||||
|
|
||||||
if not (await app_model.Apps.exists(app, project_id)):
|
if not (await app_model.Apps.exists(app, project_id)):
|
||||||
return web.json_response(data={
|
return web.json_response(data={
|
||||||
"error": {
|
"error": {
|
||||||
@ -44,10 +50,20 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
fn_app_routes = (await (await fnclient.apps.show(
|
fn_app_routes = (await (await fnclient.apps.show(
|
||||||
app, loop=c.event_loop)).routes.list(loop=c.event_loop))
|
app, loop=c.event_loop)).routes.list(loop=c.event_loop))
|
||||||
|
|
||||||
|
for fn_route in fn_app_routes:
|
||||||
|
stored_route = (await app_model.Routes.find_by(
|
||||||
|
app_name=app,
|
||||||
|
project_id=project_id,
|
||||||
|
path=fn_route.path)).pop()
|
||||||
|
setattr(fn_route, "is_public", stored_route.public)
|
||||||
|
|
||||||
|
api_url = "{}://{}".format(request.scheme, request.host)
|
||||||
log.info("Listing app {} routes for project: {}."
|
log.info("Listing app {} routes for project: {}."
|
||||||
.format(app, project_id))
|
.format(app, project_id))
|
||||||
return web.json_response(data={
|
return web.json_response(data={
|
||||||
"routes": app_view.AppRouteView(fn_app_routes).view(),
|
"routes": app_view.AppRouteView(api_url,
|
||||||
|
project_id,
|
||||||
|
fn_app_routes).view(),
|
||||||
"message": "Successfully loaded app routes",
|
"message": "Successfully loaded app routes",
|
||||||
}, status=200)
|
}, status=200)
|
||||||
|
|
||||||
@ -58,6 +74,7 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
log, fnclient = c.logger, c.functions_client
|
log, fnclient = c.logger, c.functions_client
|
||||||
project_id = request.match_info.get('project_id')
|
project_id = request.match_info.get('project_id')
|
||||||
app = request.match_info.get('app')
|
app = request.match_info.get('app')
|
||||||
|
|
||||||
if not (await app_model.Apps.exists(app, project_id)):
|
if not (await app_model.Apps.exists(app, project_id)):
|
||||||
return web.json_response(data={
|
return web.json_response(data={
|
||||||
"error": {
|
"error": {
|
||||||
@ -67,6 +84,8 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
|
|
||||||
data = (await request.json())['route']
|
data = (await request.json())['route']
|
||||||
path = data['path']
|
path = data['path']
|
||||||
|
is_public = json.loads(data.get(
|
||||||
|
'is_public', "false"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fn_app = await fnclient.apps.show(app, loop=c.event_loop)
|
fn_app = await fnclient.apps.show(app, loop=c.event_loop)
|
||||||
@ -95,15 +114,26 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
}
|
}
|
||||||
}, status=getattr(ex, "status", 500))
|
}, status=getattr(ex, "status", 500))
|
||||||
|
|
||||||
new_fn_route = (await (await fnclient.apps.show(
|
new_fn_route = (await fn_app.routes.create(
|
||||||
app, loop=c.event_loop)).routes.create(
|
|
||||||
**data, loop=c.event_loop))
|
**data, loop=c.event_loop))
|
||||||
|
|
||||||
|
stored_route = await app_model.Routes(
|
||||||
|
app_name=new_fn_route.appname,
|
||||||
|
project_id=project_id,
|
||||||
|
path=new_fn_route.path,
|
||||||
|
is_public=is_public).save()
|
||||||
|
|
||||||
log.info("Creating new route in app {} "
|
log.info("Creating new route in app {} "
|
||||||
"for project: {} with data {}"
|
"for project: {} with data {}"
|
||||||
.format(app, project_id, str(data)))
|
.format(app, project_id, str(data)))
|
||||||
|
api_url = "{}://{}".format(request.scheme, request.host)
|
||||||
|
|
||||||
|
setattr(new_fn_route, "is_public", stored_route.public)
|
||||||
|
view = app_view.AppRouteView(
|
||||||
|
api_url, project_id, [new_fn_route]).view()
|
||||||
|
|
||||||
return web.json_response(data={
|
return web.json_response(data={
|
||||||
"route": app_view.AppRouteView([new_fn_route]).view(),
|
"route": view,
|
||||||
"message": "App route successfully created"
|
"message": "App route successfully created"
|
||||||
}, status=200)
|
}, status=200)
|
||||||
|
|
||||||
@ -116,6 +146,13 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
app = request.match_info.get('app')
|
app = request.match_info.get('app')
|
||||||
path = request.match_info.get('route')
|
path = request.match_info.get('route')
|
||||||
|
|
||||||
|
if not (await app_model.Apps.exists(app, project_id)):
|
||||||
|
return web.json_response(data={
|
||||||
|
"error": {
|
||||||
|
"message": "App {0} not found".format(app),
|
||||||
|
}
|
||||||
|
}, status=404)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fn_app = await fnclient.apps.show(app, loop=c.event_loop)
|
fn_app = await fnclient.apps.show(app, loop=c.event_loop)
|
||||||
route = await fn_app.routes.show(
|
route = await fn_app.routes.show(
|
||||||
@ -129,8 +166,20 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
|
|
||||||
log.info("Requesting route {} in app {} for project: {}"
|
log.info("Requesting route {} in app {} for project: {}"
|
||||||
.format(path, app, project_id))
|
.format(path, app, project_id))
|
||||||
|
|
||||||
|
api_url = "{}://{}".format(request.scheme, request.host)
|
||||||
|
|
||||||
|
stored_route = (await app_model.Routes.find_by(
|
||||||
|
app_name=app,
|
||||||
|
project_id=project_id,
|
||||||
|
path=route.path)).pop()
|
||||||
|
|
||||||
|
setattr(route, "is_public", stored_route.public)
|
||||||
|
|
||||||
return web.json_response(data={
|
return web.json_response(data={
|
||||||
"route": app_view.AppRouteView([route]).view().pop(),
|
"route": app_view.AppRouteView(api_url,
|
||||||
|
project_id,
|
||||||
|
[route]).view().pop(),
|
||||||
"message": "App route successfully loaded"
|
"message": "App route successfully loaded"
|
||||||
}, status=200)
|
}, status=200)
|
||||||
|
|
||||||
@ -144,10 +193,19 @@ class AppRouteV1Controller(controllers.ServiceControllerBase):
|
|||||||
path = request.match_info.get('route')
|
path = request.match_info.get('route')
|
||||||
log.info("Deleting route {} in app {} for project: {}"
|
log.info("Deleting route {} in app {} for project: {}"
|
||||||
.format(path, app, project_id))
|
.format(path, app, project_id))
|
||||||
|
|
||||||
|
if not (await app_model.Apps.exists(app, project_id)):
|
||||||
|
return web.json_response(data={
|
||||||
|
"error": {
|
||||||
|
"message": "App {0} not found".format(app),
|
||||||
|
}
|
||||||
|
}, status=404)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fn_app = await fnclient.apps.show(app, loop=c.event_loop)
|
fn_app = await fnclient.apps.show(app, loop=c.event_loop)
|
||||||
await fn_app.routes.show("/{}".format(path), loop=c.event_loop)
|
await fn_app.routes.show("/{}".format(path), loop=c.event_loop)
|
||||||
await fn_app.routes.delete("/{}".format(path), loop=c.event_loop)
|
await fn_app.routes.delete("/{}".format(path), loop=c.event_loop)
|
||||||
|
await app_model.Routes.delete(project_id=project_id, app_name=app)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
return web.json_response(data={
|
return web.json_response(data={
|
||||||
"error": {
|
"error": {
|
||||||
|
@ -18,14 +18,8 @@ from laos.common.base import controllers
|
|||||||
from laos.common import config
|
from laos.common import config
|
||||||
|
|
||||||
|
|
||||||
class RunnableV1Controller(controllers.ServiceControllerBase):
|
class RunnableMixin(object):
|
||||||
|
|
||||||
controller_name = "runnable"
|
|
||||||
# IronFunction uses `r` as runnable instead API version
|
|
||||||
version = "r"
|
|
||||||
|
|
||||||
@controllers.api_action(
|
|
||||||
method='POST', route='{project_id}/{app}/{route}')
|
|
||||||
async def run(self, request, **kwargs):
|
async def run(self, request, **kwargs):
|
||||||
c = config.Config.config_instance()
|
c = config.Config.config_instance()
|
||||||
fnclient = c.functions_client
|
fnclient = c.functions_client
|
||||||
@ -68,3 +62,31 @@ class RunnableV1Controller(controllers.ServiceControllerBase):
|
|||||||
return _data
|
return _data
|
||||||
|
|
||||||
return web.json_response(status=200, data=process_result(result))
|
return web.json_response(status=200, data=process_result(result))
|
||||||
|
|
||||||
|
|
||||||
|
class PublicRunnableV1Controller(controllers.ServiceControllerBase,
|
||||||
|
RunnableMixin):
|
||||||
|
|
||||||
|
controller_name = "public_runnable"
|
||||||
|
# IronFunction uses `r` as runnable instead API version
|
||||||
|
version = "r"
|
||||||
|
|
||||||
|
@controllers.api_action(
|
||||||
|
method='POST', route='{app}/{route}')
|
||||||
|
async def run(self, request, **kwargs):
|
||||||
|
return await super(PublicRunnableV1Controller,
|
||||||
|
self).run(request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RunnableV1Controller(controllers.ServiceControllerBase,
|
||||||
|
RunnableMixin):
|
||||||
|
|
||||||
|
controller_name = "runnable"
|
||||||
|
# IronFunction uses `r` as runnable instead API version
|
||||||
|
version = "r"
|
||||||
|
|
||||||
|
@controllers.api_action(
|
||||||
|
method='POST', route='{project_id}/{app}/{route}')
|
||||||
|
async def run(self, request, **kwargs):
|
||||||
|
return await super(RunnableV1Controller,
|
||||||
|
self).run(request, **kwargs)
|
||||||
|
@ -27,6 +27,12 @@ class TasksV1Controller(controllers.ServiceControllerBase):
|
|||||||
controller_name = "tasks"
|
controller_name = "tasks"
|
||||||
version = "v1"
|
version = "v1"
|
||||||
|
|
||||||
|
# TODO(denismakogon):
|
||||||
|
# - define subapp to process requests to tasks API:
|
||||||
|
# * extract tasks V1 controller to subapp
|
||||||
|
# - on each request check if route is public our private
|
||||||
|
# * reject with 401 if route is private
|
||||||
|
# * accept with 200 if route is public
|
||||||
@controllers.api_action(
|
@controllers.api_action(
|
||||||
method='GET', route='{project_id}/tasks')
|
method='GET', route='{project_id}/tasks')
|
||||||
async def get(self, request, **kwargs):
|
async def get(self, request, **kwargs):
|
||||||
|
@ -18,12 +18,13 @@ async def content_type_validator(app: web.Application, handler):
|
|||||||
async def middleware_handler(request: web.Request):
|
async def middleware_handler(request: web.Request):
|
||||||
headers = request.headers
|
headers = request.headers
|
||||||
content_type = headers.get("Content-Type")
|
content_type = headers.get("Content-Type")
|
||||||
if "application/json" not in content_type:
|
if request.has_body:
|
||||||
return web.json_response(
|
if "application/json" != content_type:
|
||||||
data={
|
return web.json_response(
|
||||||
"error": {
|
data={
|
||||||
"message": "Invalid content type"
|
"error": {
|
||||||
}
|
"message": "Invalid content type"
|
||||||
}, status=400)
|
}
|
||||||
|
}, status=400)
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
return middleware_handler
|
return middleware_handler
|
||||||
|
@ -33,16 +33,26 @@ class AppView(object):
|
|||||||
|
|
||||||
class AppRouteView(object):
|
class AppRouteView(object):
|
||||||
|
|
||||||
def __init__(self, fn_app_routes):
|
def __init__(self, api_url, project_id, fn_app_routes):
|
||||||
self.routes = fn_app_routes
|
self.routes = fn_app_routes
|
||||||
|
self.api_url = api_url
|
||||||
|
self.project_id = project_id
|
||||||
|
|
||||||
def view(self):
|
def view(self):
|
||||||
view = []
|
view = []
|
||||||
for route in self.routes:
|
for route in self.routes:
|
||||||
|
if not route.is_public:
|
||||||
|
path = ("{}/private/{}/{}{}".format(
|
||||||
|
self.api_url, self.project_id,
|
||||||
|
route.appname, route.path))
|
||||||
|
else:
|
||||||
|
path = ("{}/public/{}{}".format(
|
||||||
|
self.api_url, route.appname, route.path))
|
||||||
view.append({
|
view.append({
|
||||||
"path": route.path,
|
"path": path,
|
||||||
"type": route.type,
|
"type": route.type,
|
||||||
"memory": route.memory,
|
"memory": route.memory,
|
||||||
"image": route.image,
|
"image": route.image,
|
||||||
|
"is_public": route.is_public,
|
||||||
})
|
})
|
||||||
return view
|
return view
|
||||||
|
@ -35,14 +35,13 @@ class ServiceControllerBase(object):
|
|||||||
method.arg_method,
|
method.arg_method,
|
||||||
method.arg_route] for method in methods]
|
method.arg_route] for method in methods]
|
||||||
|
|
||||||
def __init__(self, service: web.Application):
|
def __init__(self, sub_service: web.Application):
|
||||||
for fn, http_method, route in self.__get_handlers():
|
for fn, http_method, route in self.__get_handlers():
|
||||||
proxy_fn = '_'.join([fn.__name__, self.controller_name])
|
proxy_fn = '_'.join([fn.__name__, self.controller_name])
|
||||||
setattr(self, proxy_fn, fn)
|
setattr(self, proxy_fn, fn)
|
||||||
service.router.add_route(http_method,
|
sub_service.router.add_route(
|
||||||
"/{}/{}".format(self.version, route),
|
http_method, "/{}".format(route),
|
||||||
getattr(self, proxy_fn),
|
getattr(self, proxy_fn), name=proxy_fn)
|
||||||
name=proxy_fn)
|
|
||||||
|
|
||||||
|
|
||||||
def api_action(**outter_kwargs):
|
def api_action(**outter_kwargs):
|
||||||
|
@ -13,11 +13,8 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from laos.common.base import controllers as c
|
|
||||||
from laos.common import logger as log
|
from laos.common import logger as log
|
||||||
|
|
||||||
|
|
||||||
@ -25,49 +22,61 @@ class AbstractWebServer(object):
|
|||||||
|
|
||||||
def __init__(self, host: str='127.0.0.1',
|
def __init__(self, host: str='127.0.0.1',
|
||||||
port: int= '10001',
|
port: int= '10001',
|
||||||
controllers: typing.List[c.ServiceControllerBase]=None,
|
private_controllers: dict=None,
|
||||||
middlewares: list=None,
|
private_middlewares: list=None,
|
||||||
|
public_middlewares: list=None,
|
||||||
|
public_controllers: dict=None,
|
||||||
event_loop: asyncio.AbstractEventLoop=None,
|
event_loop: asyncio.AbstractEventLoop=None,
|
||||||
logger=log.UnifiedLogger(
|
logger=log.UnifiedLogger(
|
||||||
log_to_console=True,
|
log_to_console=True,
|
||||||
level="INFO").setup_logger(__name__)):
|
level="INFO").setup_logger(__name__),
|
||||||
|
debug=False):
|
||||||
"""
|
"""
|
||||||
HTTP server abstraction class
|
HTTP server abstraction class
|
||||||
:param host:
|
:param host: Bind host
|
||||||
:param port:
|
:param port: Bind port
|
||||||
:param controllers:
|
:param private_controllers: private API controllers mapping
|
||||||
:param middlewares:
|
:param private_middlewares: list of private API middleware
|
||||||
:param event_loop:
|
:param public_middlewares:
|
||||||
:param logger:
|
list of public API middleware
|
||||||
|
:param public_controllers:
|
||||||
|
public API controllers mapping
|
||||||
|
:param event_loop: asyncio eventloop
|
||||||
|
:param logger: logging.Logger
|
||||||
"""
|
"""
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.controllers = controllers
|
|
||||||
self.event_loop = event_loop
|
self.event_loop = event_loop
|
||||||
self.service = web.Application(
|
|
||||||
loop=self.event_loop,
|
|
||||||
debug=os.environ.get('PYTHONASYNCIODEBUG', 0),
|
|
||||||
middlewares=middlewares if middlewares else [])
|
|
||||||
self.service_handler = None
|
|
||||||
self.server = None
|
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
def _apply_routers(self):
|
self.root_service = web.Application(
|
||||||
if self.controllers:
|
logger=self.logger,
|
||||||
for controller in self.controllers:
|
loop=self.event_loop,
|
||||||
controller(self.service)
|
debug=debug
|
||||||
|
)
|
||||||
|
|
||||||
def shutdown(self):
|
self.register_subapps(private_controllers, private_middlewares)
|
||||||
self.server.close()
|
self.register_subapps(public_controllers, public_middlewares)
|
||||||
self.event_loop.run_until_complete(self.server.wait_closed())
|
|
||||||
self.event_loop.run_until_complete(
|
def _apply_routers(self, service, controllers):
|
||||||
self.service_handler.finish_connections(1.0))
|
for controller in controllers:
|
||||||
self.event_loop.run_until_complete(self.service.cleanup())
|
controller(service)
|
||||||
|
return service
|
||||||
|
|
||||||
|
def register_subapps(self, controllers_mapping: dict, middlewares: list):
|
||||||
|
if controllers_mapping:
|
||||||
|
for sub_route, controllers in controllers_mapping.items():
|
||||||
|
service = self._apply_routers(
|
||||||
|
web.Application(
|
||||||
|
logger=self.logger,
|
||||||
|
loop=self.event_loop,
|
||||||
|
middlewares=middlewares
|
||||||
|
if middlewares else []
|
||||||
|
)
|
||||||
|
, controllers)
|
||||||
|
self.root_service.router.add_subapp(
|
||||||
|
"/{}/".format(sub_route), service)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self._apply_routers()
|
web.run_app(self.root_service, host=self.host, port=self.port,
|
||||||
try:
|
shutdown_timeout=10, access_log=self.logger)
|
||||||
web.run_app(self.service, host=self.host, port=self.port,
|
|
||||||
shutdown_timeout=10, access_log=self.logger)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self.shutdown()
|
|
||||||
|
@ -76,7 +76,10 @@ class UnifiedLogger(object, metaclass=utils.Singleton):
|
|||||||
if 'DEBUG' not in level:
|
if 'DEBUG' not in level:
|
||||||
self.log_formatter = (
|
self.log_formatter = (
|
||||||
'[%(asctime)s] - '
|
'[%(asctime)s] - '
|
||||||
'%(message)s')
|
'%(name)s - '
|
||||||
|
'%(module)s.py:%(lineno)d - '
|
||||||
|
'%(message)s'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self. log_formatter = (
|
self. log_formatter = (
|
||||||
'[%(asctime)s] - '
|
'[%(asctime)s] - '
|
||||||
|
@ -27,6 +27,10 @@ class BaseDatabaseModel(object):
|
|||||||
DELETE = "DELETE FROM {} {}"
|
DELETE = "DELETE FROM {} {}"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
logger = config.Config.config_instance().logger
|
||||||
|
logger.info("Attempting to create object class instance "
|
||||||
|
"'{}' with attributes '{}'"
|
||||||
|
.format(str(self.__class__), str(kwargs)))
|
||||||
self.id = uuid.uuid4().hex
|
self.id = uuid.uuid4().hex
|
||||||
self.created_at = str(datetime.datetime.now())
|
self.created_at = str(datetime.datetime.now())
|
||||||
self.updated_at = str(datetime.datetime.now())
|
self.updated_at = str(datetime.datetime.now())
|
||||||
@ -34,30 +38,37 @@ class BaseDatabaseModel(object):
|
|||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
async def save(self):
|
async def save(self):
|
||||||
|
logger = config.Config.config_instance().logger
|
||||||
insert = self.INSERT.format(
|
insert = self.INSERT.format(
|
||||||
self.table_name,
|
self.table_name,
|
||||||
str(tuple([getattr(self, clmn) for clmn in self.columns]))
|
str(tuple([getattr(self, clmn) for clmn in self.columns]))
|
||||||
)
|
)
|
||||||
print(insert)
|
logger.info("Attempting to save object '{}' "
|
||||||
|
"using SQL query: {}".format(self.table_name, insert))
|
||||||
async with config.Connection.from_class().acquire() as conn:
|
async with config.Connection.from_class().acquire() as conn:
|
||||||
async with conn.cursor() as cur:
|
async with conn.cursor() as cur:
|
||||||
await cur.execute(insert)
|
await cur.execute(insert)
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
|
logger.info("Object saved.")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def delete(cls, **kwargs):
|
async def delete(cls, **kwargs):
|
||||||
|
logger = config.Config.config_instance().logger
|
||||||
delete = cls.DELETE.format(
|
delete = cls.DELETE.format(
|
||||||
cls.table_name, cls.__define_where(**kwargs))
|
cls.table_name, cls.__define_where(**kwargs))
|
||||||
|
logger.info("Attempting to delete object '{}' "
|
||||||
|
"using SQL query: {}".format(cls.table_name, delete))
|
||||||
async with config.Connection.from_class().acquire() as conn:
|
async with config.Connection.from_class().acquire() as conn:
|
||||||
async with conn.cursor() as cur:
|
async with conn.cursor() as cur:
|
||||||
await cur.execute(delete)
|
await cur.execute(delete)
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
|
logger.info("Object gone.")
|
||||||
|
|
||||||
async def update(self, **kwargs):
|
# async def update(self, **kwargs):
|
||||||
async with config.Connection.from_class().acquire() as conn:
|
# async with config.Connection.from_class().acquire() as conn:
|
||||||
async with conn.cursor() as cur:
|
# async with conn.cursor() as cur:
|
||||||
await cur.execute()
|
# await cur.execute()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def exists(cls, name, project_id):
|
async def exists(cls, name, project_id):
|
||||||
@ -81,12 +92,15 @@ class BaseDatabaseModel(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def find_by(cls, **kwargs):
|
async def find_by(cls, **kwargs):
|
||||||
|
logger = config.Config.config_instance().logger
|
||||||
where = cls.__define_where(**kwargs)
|
where = cls.__define_where(**kwargs)
|
||||||
|
select = cls.SELECT.format(
|
||||||
|
cls.table_name, where)
|
||||||
|
logger.info("Attempting to find object(s) '{}' "
|
||||||
|
"using SQL : {}".format(cls.table_name, select))
|
||||||
async with config.Connection.from_class().acquire() as conn:
|
async with config.Connection.from_class().acquire() as conn:
|
||||||
async with conn.cursor() as cur:
|
async with conn.cursor() as cur:
|
||||||
await cur.execute(cls.SELECT.format(
|
await cur.execute(select)
|
||||||
cls.table_name, where))
|
|
||||||
results = await cur.fetchall()
|
results = await cur.fetchall()
|
||||||
return [cls.from_tuple(instance)
|
return [cls.from_tuple(instance)
|
||||||
for instance in results] if results else []
|
for instance in results] if results else []
|
||||||
|
@ -26,3 +26,20 @@ class Apps(persistence.BaseDatabaseModel):
|
|||||||
"updated_at",
|
"updated_at",
|
||||||
"name"
|
"name"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Routes(persistence.BaseDatabaseModel):
|
||||||
|
|
||||||
|
table_name = "routes"
|
||||||
|
columns = (
|
||||||
|
"project_id",
|
||||||
|
"path",
|
||||||
|
"is_public",
|
||||||
|
"app_name",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def public(self):
|
||||||
|
return True if self.is_public else False
|
||||||
|
@ -35,22 +35,36 @@ class API(service.AbstractWebServer):
|
|||||||
def __init__(self, host: str='0.0.0.0',
|
def __init__(self, host: str='0.0.0.0',
|
||||||
port: int=10001,
|
port: int=10001,
|
||||||
loop: asyncio.AbstractEventLoop=asyncio.get_event_loop(),
|
loop: asyncio.AbstractEventLoop=asyncio.get_event_loop(),
|
||||||
logger=None):
|
logger=None,
|
||||||
|
debug=False):
|
||||||
super(API, self).__init__(
|
super(API, self).__init__(
|
||||||
host=host,
|
host=host,
|
||||||
port=port,
|
port=port,
|
||||||
controllers=[
|
private_controllers={
|
||||||
apps.AppV1Controller,
|
"v1": [
|
||||||
routes.AppRouteV1Controller,
|
apps.AppV1Controller,
|
||||||
runnable.RunnableV1Controller,
|
routes.AppRouteV1Controller,
|
||||||
tasks.TasksV1Controller,
|
tasks.TasksV1Controller,
|
||||||
],
|
],
|
||||||
middlewares=[
|
"private": [
|
||||||
|
runnable.RunnableV1Controller,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
public_controllers={
|
||||||
|
"public": [
|
||||||
|
runnable.PublicRunnableV1Controller,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
private_middlewares=[
|
||||||
keystone.auth_through_token,
|
keystone.auth_through_token,
|
||||||
content_type.content_type_validator,
|
content_type.content_type_validator,
|
||||||
],
|
],
|
||||||
|
public_middlewares=[
|
||||||
|
content_type.content_type_validator,
|
||||||
|
],
|
||||||
event_loop=loop,
|
event_loop=loop,
|
||||||
logger=logger,
|
logger=logger,
|
||||||
|
debug=debug,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -73,6 +87,7 @@ class API(service.AbstractWebServer):
|
|||||||
help='Logging file')
|
help='Logging file')
|
||||||
@click.option('--log-file', default=None,
|
@click.option('--log-file', default=None,
|
||||||
help='Log file path')
|
help='Log file path')
|
||||||
|
@click.option('--debug', default=False, is_flag=True)
|
||||||
def server(host, port, db_uri,
|
def server(host, port, db_uri,
|
||||||
keystone_endpoint,
|
keystone_endpoint,
|
||||||
functions_host,
|
functions_host,
|
||||||
@ -81,6 +96,7 @@ def server(host, port, db_uri,
|
|||||||
functions_api_protocol,
|
functions_api_protocol,
|
||||||
log_level,
|
log_level,
|
||||||
log_file,
|
log_file,
|
||||||
|
debug,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Starts an Project Laos API service
|
Starts an Project Laos API service
|
||||||
@ -88,7 +104,7 @@ def server(host, port, db_uri,
|
|||||||
logger = log.UnifiedLogger(
|
logger = log.UnifiedLogger(
|
||||||
log_to_console=True if not log_file else False,
|
log_to_console=True if not log_file else False,
|
||||||
filename=None if not log_file else log_file,
|
filename=None if not log_file else log_file,
|
||||||
level=log_level).setup_logger(__name__)
|
level=log_level).setup_logger(__package__)
|
||||||
|
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
@ -104,17 +120,14 @@ def server(host, port, db_uri,
|
|||||||
|
|
||||||
config.Config(
|
config.Config(
|
||||||
auth_url=keystone_endpoint,
|
auth_url=keystone_endpoint,
|
||||||
functions_host=functions_host,
|
|
||||||
functions_port=functions_port,
|
|
||||||
functions_api_protocol=functions_api_protocol,
|
|
||||||
functions_api_version=functions_api_version,
|
|
||||||
functions_client=fnclient,
|
functions_client=fnclient,
|
||||||
logger=logger,
|
logger=logger,
|
||||||
connection=conn,
|
connection=conn,
|
||||||
event_loop=loop,
|
event_loop=loop,
|
||||||
)
|
)
|
||||||
|
|
||||||
API(host=host, port=port, loop=loop, logger=logger).initialize()
|
API(host=host, port=port, loop=loop,
|
||||||
|
logger=logger, debug=debug).initialize()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
0
laos/tests/__init__.py
Normal file
0
laos/tests/__init__.py
Normal file
@ -27,7 +27,17 @@ def upgrade():
|
|||||||
sa.Column('updated_at', sa.String(255)),
|
sa.Column('updated_at', sa.String(255)),
|
||||||
sa.Column('name', sa.String(255), nullable=False, primary_key=True),
|
sa.Column('name', sa.String(255), nullable=False, primary_key=True),
|
||||||
)
|
)
|
||||||
|
op.create_table(
|
||||||
|
'routes',
|
||||||
|
sa.Column('project_id', sa.String(255), nullable=False),
|
||||||
|
sa.Column('path', sa.String(255), nullable=False, primary_key=True),
|
||||||
|
sa.Column('is_public', sa.Boolean(create_constraint=False), nullable=False),
|
||||||
|
sa.Column('app_name', sa.String(255), nullable=False, primary_key=True),
|
||||||
|
sa.Column('created_at', sa.String(255), nullable=False),
|
||||||
|
sa.Column('updated_at', sa.String(255), nullable=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
|
op.drop_table('routes')
|
||||||
op.drop_table('apps')
|
op.drop_table('apps')
|
||||||
|
@ -9,4 +9,3 @@ alembic>=0.8.4 # MIT
|
|||||||
click
|
click
|
||||||
keystoneauth1>=2.14.0 # Apache-2.0
|
keystoneauth1>=2.14.0 # Apache-2.0
|
||||||
python-keystoneclient==3.6.0
|
python-keystoneclient==3.6.0
|
||||||
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
|
||||||
|
1
setup.py
1
setup.py
@ -36,7 +36,6 @@ setuptools.setup(
|
|||||||
"click",
|
"click",
|
||||||
"keystoneauth1>=2.14.0",
|
"keystoneauth1>=2.14.0",
|
||||||
"python-keystoneclient==3.6.0",
|
"python-keystoneclient==3.6.0",
|
||||||
"SQLAlchemy<1.1.0,>=1.0.10",
|
|
||||||
],
|
],
|
||||||
license='License :: OSI Approved :: Apache Software License',
|
license='License :: OSI Approved :: Apache Software License',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
Loading…
x
Reference in New Issue
Block a user