159 lines
3.9 KiB
Python
159 lines
3.9 KiB
Python
|
import logging
|
||
|
|
||
|
from flask import Blueprint, request, abort
|
||
|
from flask_restful import Resource, Api
|
||
|
from flask_restful.utils.cors import crossdomain
|
||
|
from data import model
|
||
|
from email.utils import formatdate
|
||
|
from calendar import timegm
|
||
|
from functools import partial, wraps
|
||
|
from jsonschema import validate, ValidationError
|
||
|
|
||
|
from config_app.c_app import app, IS_KUBERNETES
|
||
|
from config_app.config_endpoints.exception import InvalidResponse, InvalidRequest
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
api_bp = Blueprint('api', __name__)
|
||
|
|
||
|
CROSS_DOMAIN_HEADERS = ['Authorization', 'Content-Type', 'X-Requested-With']
|
||
|
|
||
|
|
||
|
class ApiExceptionHandlingApi(Api):
|
||
|
pass
|
||
|
|
||
|
@crossdomain(origin='*', headers=CROSS_DOMAIN_HEADERS)
|
||
|
def handle_error(self, error):
|
||
|
return super(ApiExceptionHandlingApi, self).handle_error(error)
|
||
|
|
||
|
|
||
|
api = ApiExceptionHandlingApi()
|
||
|
|
||
|
api.init_app(api_bp)
|
||
|
|
||
|
def log_action(kind, user_or_orgname, metadata=None, repo=None, repo_name=None):
|
||
|
if not metadata:
|
||
|
metadata = {}
|
||
|
|
||
|
if repo:
|
||
|
repo_name = repo.name
|
||
|
|
||
|
model.log.log_action(kind, user_or_orgname, repo_name, user_or_orgname, request.remote_addr, metadata)
|
||
|
|
||
|
def format_date(date):
|
||
|
""" Output an RFC822 date format. """
|
||
|
if date is None:
|
||
|
return None
|
||
|
return formatdate(timegm(date.utctimetuple()))
|
||
|
|
||
|
|
||
|
|
||
|
def resource(*urls, **kwargs):
|
||
|
def wrapper(api_resource):
|
||
|
if not api_resource:
|
||
|
return None
|
||
|
|
||
|
api_resource.registered = True
|
||
|
api.add_resource(api_resource, *urls, **kwargs)
|
||
|
return api_resource
|
||
|
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
class ApiResource(Resource):
|
||
|
registered = False
|
||
|
method_decorators = []
|
||
|
|
||
|
def options(self):
|
||
|
return None, 200
|
||
|
|
||
|
|
||
|
def add_method_metadata(name, value):
|
||
|
def modifier(func):
|
||
|
if func is None:
|
||
|
return None
|
||
|
|
||
|
if '__api_metadata' not in dir(func):
|
||
|
func.__api_metadata = {}
|
||
|
func.__api_metadata[name] = value
|
||
|
return func
|
||
|
|
||
|
return modifier
|
||
|
|
||
|
|
||
|
def method_metadata(func, name):
|
||
|
if func is None:
|
||
|
return None
|
||
|
|
||
|
if '__api_metadata' in dir(func):
|
||
|
return func.__api_metadata.get(name, None)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def no_cache(f):
|
||
|
@wraps(f)
|
||
|
def add_no_cache(*args, **kwargs):
|
||
|
response = f(*args, **kwargs)
|
||
|
if response is not None:
|
||
|
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||
|
return response
|
||
|
return add_no_cache
|
||
|
|
||
|
|
||
|
def define_json_response(schema_name):
|
||
|
def wrapper(func):
|
||
|
@add_method_metadata('response_schema', schema_name)
|
||
|
@wraps(func)
|
||
|
def wrapped(self, *args, **kwargs):
|
||
|
schema = self.schemas[schema_name]
|
||
|
resp = func(self, *args, **kwargs)
|
||
|
|
||
|
if app.config['TESTING']:
|
||
|
try:
|
||
|
validate(resp, schema)
|
||
|
except ValidationError as ex:
|
||
|
raise InvalidResponse(ex.message)
|
||
|
|
||
|
return resp
|
||
|
return wrapped
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def validate_json_request(schema_name, optional=False):
|
||
|
def wrapper(func):
|
||
|
@add_method_metadata('request_schema', schema_name)
|
||
|
@wraps(func)
|
||
|
def wrapped(self, *args, **kwargs):
|
||
|
schema = self.schemas[schema_name]
|
||
|
try:
|
||
|
json_data = request.get_json()
|
||
|
if json_data is None:
|
||
|
if not optional:
|
||
|
raise InvalidRequest('Missing JSON body')
|
||
|
else:
|
||
|
validate(json_data, schema)
|
||
|
return func(self, *args, **kwargs)
|
||
|
except ValidationError as ex:
|
||
|
raise InvalidRequest(ex.message)
|
||
|
return wrapped
|
||
|
return wrapper
|
||
|
|
||
|
def kubernetes_only(f):
|
||
|
""" Aborts the request with a 400 if the app is not running on kubernetes """
|
||
|
@wraps(f)
|
||
|
def abort_if_not_kube(*args, **kwargs):
|
||
|
if not IS_KUBERNETES:
|
||
|
abort(400)
|
||
|
|
||
|
return f(*args, **kwargs)
|
||
|
return abort_if_not_kube
|
||
|
|
||
|
nickname = partial(add_method_metadata, 'nickname')
|
||
|
|
||
|
|
||
|
import config_app.config_endpoints.api.discovery
|
||
|
import config_app.config_endpoints.api.kube_endpoints
|
||
|
import config_app.config_endpoints.api.suconfig
|
||
|
import config_app.config_endpoints.api.superuser
|
||
|
import config_app.config_endpoints.api.tar_config_loader
|
||
|
import config_app.config_endpoints.api.user
|