Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Endpoint configuration #357

Merged
merged 10 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ To lint your local changes, run
rq_custom_worker job_queue_0 -c /etc/default/actinia
```

## Local dev-setup with configured endpoints
- add a endpoints configuration csv file like `docker/actinia-core-dev/endpoints.csv`
with all desired endpoints including method:
```
Class_of_the_endpoint;method1,method2
Class_of_the_endpoint2;method1
```
- make sure that the file is added in the `docker/actiia-core-dev/Dockerfile` with e.g. `COPY docker/actinia-core-dev/endpoints.csv /etc/default/actinia_endpoints.csv`
- add `endpoints_config = /etc/default/actinia_endpoints.csv` to the `API` section in the `docker/actinia-core-dev/actinia.cfg` file
- then build and run actinia dev-setup as normally

<a id="grass-gis"></a>
# Testing GRASS GIS inside a container
Expand Down
1 change: 1 addition & 0 deletions docker/actinia-core-dev/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ RUN pip3 uninstall actinia-core -y
RUN pip3 uninstall actinia_core -y

COPY docker/actinia-core-dev/actinia.cfg /etc/default/actinia
COPY docker/actinia-core-dev/endpoints.csv /etc/default/actinia_endpoints.csv
COPY . /src/actinia_core/

RUN git config --global --add safe.directory /src/actinia*
Expand Down
1 change: 1 addition & 0 deletions docker/actinia-core-dev/actinia.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ additional_allowed_modules=i.albedo
[API]
plugins = []
force_https_urls = False
# endpoints_config = /etc/default/actinia_endpoints.csv

[REDIS]
redis_server_url = redis
Expand Down
18 changes: 18 additions & 0 deletions docker/actinia-core-dev/endpoints.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
ListLocationsResource;GET
LocationManagementResourceUser;GET
ListMapsetsResource;GET
MapsetManagementResourceUser;GET
RasterLayersResource;GET
RasterLayerResource;GET,POST
SyncEphemeralRasterRendererResource;GET
SyncSTRDSListerResource;GET
STRDSManagementResource;GET
STRDSRasterManagement;GET
VectorLayersResource;GET
VectorLayerResource;GET
SyncEphemeralVectorRendererResource;GET
SyncEphemeralSTRDSRendererResource;GET
AllMapsetsListingResourceAdmin;GET
ResourceManager;GET
AsyncEphemeralExportResource;POST
AsyncPersistentResource;POST
5 changes: 5 additions & 0 deletions src/actinia_core/core/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ def __init__(self):
self.FORCE_HTTPS_URLS = False
# PLUGINS: e.g. ["actinia_satellite_plugin", "actinia_statistic_plugin"]
self.PLUGINS = []
# ENDPOINTS_CONFIG: configuration csv file for endpoints
self.ENDPOINTS_CONFIG = None

"""
REDIS
Expand Down Expand Up @@ -302,6 +304,7 @@ def write(self, path=DEFAULT_CONFIG_PATH):
config.set('API', 'LOGIN_REQUIRED', str(self.LOGIN_REQUIRED))
config.set('API', 'FORCE_HTTPS_URLS', str(self.FORCE_HTTPS_URLS))
config.set('API', 'PLUGINS', str(self.PLUGINS))
config.set('API', 'ENDPOINTS_CONFIG', str(self.ENDPOINTS_CONFIG))

config.add_section('REDIS')
config.set('REDIS', 'REDIS_SERVER_URL', self.REDIS_SERVER_URL)
Expand Down Expand Up @@ -435,6 +438,8 @@ def read(self, path=DEFAULT_CONFIG_PATH):
self.FORCE_HTTPS_URLS = config.getboolean("API", "FORCE_HTTPS_URLS")
if config.has_option("API", "PLUGINS"):
self.PLUGINS = ast.literal_eval(config.get("API", "PLUGINS"))
if config.has_option("API", "ENDPOINTS_CONFIG"):
self.ENDPOINTS_CONFIG = config.get("API", "ENDPOINTS_CONFIG")

if config.has_section("REDIS"):
if config.has_option("REDIS", "REDIS_SERVER_URL"):
Expand Down
87 changes: 87 additions & 0 deletions src/actinia_core/core/common/endpoint_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
#######
# actinia-core - an open source REST API for scalable, distributed, high
# performance processing of geographical data that uses GRASS GIS for
# computational tasks. For details, see https://actinia.mundialis.de/
#
# Copyright (c) 2016-2018 Sören Gebbert and mundialis GmbH & Co. KG
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#######

"""
Entpoint configuration
"""

import csv
from flask import jsonify
from flask import make_response
from functools import wraps
import os
import sys

from actinia_core.core.common.config import global_config
from actinia_core.models.response_models import SimpleResponseModel


__license__ = "GPLv3"
__author__ = "Anika Weinmann"
__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG"
__maintainer__ = "mundialis GmbH & Co. KG"


if (global_config.ENDPOINTS_CONFIG is not None and
os.path.isfile(global_config.ENDPOINTS_CONFIG)):
with open(global_config.ENDPOINTS_CONFIG, mode="r") as inp:
reader = csv.reader(inp, delimiter=';')
endpoints_dict = {
row[0]: [
method.upper() for method in row[1].split(",")
] for row in reader if len(row) == 2}
else:
endpoints_dict = None


def check_endpoint(method, api_doc=None, endpoint_class=None):
if endpoint_class is None:
endpoint_class = sys._getframe().f_back.f_code.co_name
method_upper = method.upper()
if (endpoints_dict is None or
(endpoint_class in endpoints_dict and
method_upper in endpoints_dict[endpoint_class])):
return api_doc if api_doc is not None else True
else:
return False


def endpoint_decorator():

def decorator(func):
endpoint_class, method = func.__qualname__.split(".")

@wraps(func)
def wrapper(*args, **kwargs):

if check_endpoint(method, endpoint_class=endpoint_class):
result = func(*args, **kwargs)
return result
else:
return make_response(jsonify(SimpleResponseModel(
status="error",
message="Not Found. The requested URL is not configured on"
" the server.")), 404)
return wrapper

return decorator
7 changes: 6 additions & 1 deletion src/actinia_core/rest/api_log_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
from actinia_core.core.common.app import auth
from actinia_core.core.common.api_logger import ApiLogger
from actinia_core.core.common.api_logger import log_api_call
from actinia_core.core.common.endpoint_config import (
check_endpoint,
endpoint_decorator
)
from actinia_core.models.response_models import SimpleResponseModel
from actinia_core.rest.base.user_auth import check_user_permissions

Expand Down Expand Up @@ -63,7 +67,8 @@ def __init__(self):
self.user_id = g.user.get_id()
self.user_role = g.user.get_role()

@swagger.doc(api_log_management.get_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("get", api_log_management.get_doc))
def get(self, user_id):
"""Get a list of all API calls that have been called by the provided user."""

Expand Down
11 changes: 9 additions & 2 deletions src/actinia_core/rest/download_cache_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@
from flask_restful_swagger_2 import swagger
import pickle
from actinia_api.swagger2.actinia_core.apidocs import download_cache_management

from actinia_core.rest.base.resource_base import ResourceBase
from actinia_core.core.common.redis_interface import enqueue_job
from actinia_core.core.common.api_logger import log_api_call
from actinia_core.core.common.endpoint_config import (
check_endpoint,
endpoint_decorator
)
from actinia_core.rest.base.user_auth import check_admin_role
from actinia_core.rest.base.user_auth import check_user_permissions
from actinia_core.core.common.app import auth
Expand All @@ -50,7 +55,8 @@ class SyncDownloadCacheResource(ResourceBase):
decorators = [log_api_call, check_user_permissions,
check_admin_role, auth.login_required]

@swagger.doc(download_cache_management.get_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("get", download_cache_management.get_doc))
def get(self):
"""Get the current size of the download cache"""
rdc = self.preprocess(has_json=False, has_xml=False)
Expand All @@ -63,7 +69,8 @@ def get(self):

return make_response(jsonify(response_model), http_code)

@swagger.doc(download_cache_management.delete_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("delete", download_cache_management.delete_doc))
def delete(self):
"""Clean the download cache and remove all cached data"""
rdc = self.preprocess(has_json=False, has_xml=False)
Expand Down
19 changes: 15 additions & 4 deletions src/actinia_core/rest/ephemeral_processing_with_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@
from flask_restful_swagger_2 import swagger
from actinia_api.swagger2.actinia_core.apidocs import \
ephemeral_processing_with_export
from actinia_core.rest.base.resource_base import ResourceBase

from actinia_core.core.common.redis_interface import enqueue_job
from actinia_core.core.common.endpoint_config import (
check_endpoint,
endpoint_decorator
)
from actinia_core.rest.base.resource_base import ResourceBase
from actinia_core.processing.common.ephemeral_processing_with_export \
import start_job

Expand All @@ -51,7 +56,9 @@ class AsyncEphemeralExportResource(ResourceBase):
def __init__(self, resource_id=None, iteration=None, post_url=None):
ResourceBase.__init__(self, resource_id, iteration, post_url)

@swagger.doc(ephemeral_processing_with_export.post_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint(
"post", ephemeral_processing_with_export.post_doc))
def post(self, location_name):
"""Execute a user defined process chain in an ephemeral location/mapset
and store the processing results for download.
Expand All @@ -75,7 +82,9 @@ class AsyncEphemeralExportS3Resource(ResourceBase):
def __init__(self):
ResourceBase.__init__(self)

@swagger.doc(ephemeral_processing_with_export.post_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint(
"post", ephemeral_processing_with_export.post_doc))
def post(self, location_name):
"""Execute a user defined process chain in an ephemeral location/mapset and
store the processing result in an Amazon S3 bucket
Expand All @@ -98,7 +107,9 @@ class AsyncEphemeralExportGCSResource(ResourceBase):
def __init__(self):
ResourceBase.__init__(self)

@swagger.doc(ephemeral_processing_with_export.post_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint(
"post", ephemeral_processing_with_export.post_doc))
def post(self, location_name):
"""Execute a user defined process chain in an ephemeral location/mapset
and store the processing result in an Google cloud storage bucket
Expand Down
16 changes: 12 additions & 4 deletions src/actinia_core/rest/location_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@

from actinia_core.core.common.app import auth
from actinia_core.core.common.api_logger import log_api_call
from actinia_core.core.common.endpoint_config import (
check_endpoint,
endpoint_decorator
)
from actinia_core.rest.base.user_auth import check_admin_role
from actinia_core.rest.base.user_auth import check_user_permissions
from actinia_core.models.response_models import SimpleResponseModel
Expand Down Expand Up @@ -65,7 +69,8 @@ def __init__(self):
"""
layer_type = None

@swagger.doc(location_management.get_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("get", location_management.get_doc))
def get(self):
"""Get a list of all available locations
"""
Expand Down Expand Up @@ -114,7 +119,8 @@ class LocationManagementResourceUser(ResourceBase):
def __init__(self):
ResourceBase.__init__(self)

@swagger.doc(location_management.get_user_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("get", location_management.get_user_doc))
def get(self, location_name):
"""Get the location projection and current computational region of the PERMANENT mapset
"""
Expand All @@ -141,7 +147,8 @@ class LocationManagementResourceAdmin(ResourceBase):
def __init__(self):
ResourceBase.__init__(self)

@swagger.doc(location_management.get_admin_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("delete", location_management.get_admin_doc))
def delete(self, location_name):
"""Delete an existing location and everything inside from the user database.
"""
Expand All @@ -168,7 +175,8 @@ def delete(self, location_name):
status="error",
message="location %s does not exists" % location_name)), 400)

@swagger.doc(location_management.post_admin_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("post", location_management.post_admin_doc))
def post(self, location_name):
"""Create a new location based on EPSG code in the user database.
"""
Expand Down
27 changes: 20 additions & 7 deletions src/actinia_core/rest/map_layer_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@
from flask_restful_swagger_2 import swagger
import pickle
from actinia_api.swagger2.actinia_core.apidocs import map_layer_management
from actinia_core.rest.base.resource_base import ResourceBase

from actinia_core.core.common.redis_interface import enqueue_job
from actinia_core.core.common.endpoint_config import (
check_endpoint,
endpoint_decorator
)
from actinia_core.core.request_parser import glist_parser
from actinia_core.rest.base.resource_base import ResourceBase
from actinia_core.processing.common.map_layer_management import \
list_raster_layers, remove_raster_layers, rename_raster_layers

Expand Down Expand Up @@ -181,21 +186,25 @@ class RasterLayersResource(MapsetLayersResource):
def __init__(self):
MapsetLayersResource.__init__(self, layer_type="raster")

@swagger.doc(map_layer_management.raster_get_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("get", map_layer_management.raster_get_doc))
def get(self, location_name, mapset_name):
"""Get a list of raster map layer names that are located in a specific
location/mapset
"""
return self._get(location_name, mapset_name)

@swagger.doc(map_layer_management.raster_put_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("put", map_layer_management.raster_put_doc))
def put(self, location_name, mapset_name):
"""Rename a single raster map layer or a list of raster map layers that
are located in a specific location/mapset
"""
return self._put(location_name, mapset_name)

@swagger.doc(map_layer_management.raster_delete_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint(
"delete", map_layer_management.raster_delete_doc))
def delete(self, location_name, mapset_name):
"""Delete a single raster map layer or a list of raster map layer names
that are located in a specific location/mapset
Expand All @@ -209,21 +218,25 @@ class VectorLayersResource(MapsetLayersResource):
def __init__(self):
MapsetLayersResource.__init__(self, layer_type="vector")

@swagger.doc(map_layer_management.vector_get_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("get", map_layer_management.vector_get_doc))
def get(self, location_name, mapset_name):
"""Get a list of vector map layer names that are located in a specific
location/mapset
"""
return self._get(location_name, mapset_name)

@swagger.doc(map_layer_management.vector_put_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint("put", map_layer_management.vector_put_doc))
def put(self, location_name, mapset_name):
"""Rename a single vector map layer or a list of vector map layers that
are located in a specific location/mapset
"""
return self._put(location_name, mapset_name)

@swagger.doc(map_layer_management.vector_delete_doc)
@endpoint_decorator()
@swagger.doc(check_endpoint(
"delete", map_layer_management.vector_delete_doc))
def delete(self, location_name, mapset_name):
"""Delete a single vector map layer or a list of vector map layer names
that are located in a specific location/mapset
Expand Down
Loading