Setup StackTask for plugins
* All non-admin urls are now set in the config. * All taskviews are registered in the models.py file of api.v1 ** Based in part on how keystone handles it's own plugins, where the url will be defined in the modules, and the conf simply enables them. Less configurable, but safer. * StackTask now does a startup check to confirm all expected taskviews and actions have been registered ** Means we can add more startup sanity checks in future too. * Taskviews 'default_action' is now 'default_actions' ** 'default_actions' can be overridden in conf * TaskView settings 'actions' renamed to 'additional_actions' Change-Id: Ic036407cbaf292830cbe60cbed4a8db0be5e87e3
This commit is contained in:
parent
454aa30d83
commit
e1f9a5dfe0
@ -39,9 +39,6 @@ LOGGING:
|
||||
EMAIL_SETTINGS:
|
||||
EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
|
||||
|
||||
# Application settings:
|
||||
SHOW_ACTION_ENDPOINTS: False
|
||||
|
||||
# setting to control if user name and email are allowed
|
||||
# to have different values.
|
||||
USERNAME_IS_EMAIL: True
|
||||
@ -60,6 +57,14 @@ TOKEN_SUBMISSION_URL: http://192.168.122.160:8080/token/
|
||||
# time for the token to expire in hours
|
||||
TOKEN_EXPIRE_TIME: 24
|
||||
|
||||
ACTIVE_TASKVIEWS:
|
||||
- UserRoles
|
||||
- UserDetail
|
||||
- UserResetPassword
|
||||
- UserSetPassword
|
||||
- UserList
|
||||
- RoleList
|
||||
|
||||
DEFAULT_TASK_SETTINGS:
|
||||
emails:
|
||||
initial:
|
||||
@ -110,10 +115,15 @@ DEFAULT_TASK_SETTINGS:
|
||||
# These are cascading overrides for the default settings:
|
||||
TASK_SETTINGS:
|
||||
create_project:
|
||||
# Additonal actions for views:
|
||||
# - The order of the actions matters. These will run after the
|
||||
# default action, in the given order.
|
||||
actions:
|
||||
# You can override 'default_actions' if needed for given taskviews
|
||||
# The order of the actions is order of execution.
|
||||
#
|
||||
# default_actions:
|
||||
# - NewProject
|
||||
#
|
||||
# Additonal actions for views
|
||||
# These will run after the default actions, in the given order.
|
||||
additional_actions:
|
||||
- AddAdminToProject
|
||||
- DefaultProjectResources
|
||||
notifications:
|
||||
|
@ -0,0 +1 @@
|
||||
default_app_config = 'stacktask.api.startup.APIConfig'
|
58
stacktask/api/startup.py
Normal file
58
stacktask/api/startup.py
Normal file
@ -0,0 +1,58 @@
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from stacktask.exceptions import ActionNotFound, TaskViewNotFound
|
||||
|
||||
|
||||
def check_expected_taskviews():
|
||||
expected_taskviews = settings.ACTIVE_TASKVIEWS
|
||||
|
||||
missing_taskviews = list(
|
||||
set(expected_taskviews) - set(settings.TASKVIEW_CLASSES.keys()))
|
||||
|
||||
if missing_taskviews:
|
||||
raise TaskViewNotFound(
|
||||
message=(
|
||||
"Expected taskviews are unregistered: %s" % missing_taskviews))
|
||||
|
||||
|
||||
def check_expected_actions():
|
||||
"""Check that all the expected actions have been registered."""
|
||||
expected_actions = []
|
||||
|
||||
for taskview in settings.ACTIVE_TASKVIEWS:
|
||||
task_class = settings.TASKVIEW_CLASSES.get(taskview)['class']
|
||||
|
||||
try:
|
||||
expected_actions += settings.TASK_SETTINGS.get(
|
||||
task_class.task_type, {})['default_actions']
|
||||
except KeyError:
|
||||
expected_actions += task_class.default_actions
|
||||
expected_actions += settings.TASK_SETTINGS.get(
|
||||
task_class.task_type, {}).get('additional_actions', [])
|
||||
|
||||
missing_actions = list(
|
||||
set(expected_actions) - set(settings.ACTION_CLASSES.keys()))
|
||||
|
||||
if missing_actions:
|
||||
raise ActionNotFound(
|
||||
"Expected actions are unregistered: %s" % missing_actions)
|
||||
|
||||
|
||||
class APIConfig(AppConfig):
|
||||
name = 'stacktask.api'
|
||||
|
||||
def ready(self):
|
||||
"""A pre-startup function for the api.
|
||||
|
||||
Code run here will occur before the API is up and active but after
|
||||
all models have been loaded.
|
||||
|
||||
Useful for any start up checks.
|
||||
|
||||
"""
|
||||
|
||||
# First check that all expect taskviews are present
|
||||
check_expected_taskviews()
|
||||
|
||||
# Now check if all the actions those views expecte are present.
|
||||
check_expected_actions()
|
@ -12,4 +12,33 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
from stacktask.api.v1 import tasks
|
||||
from stacktask.api.v1 import openstack
|
||||
|
||||
|
||||
def register_taskview_class(url, taskview_class):
|
||||
data = {}
|
||||
data[taskview_class.__name__] = {
|
||||
'class': taskview_class,
|
||||
'url': url}
|
||||
settings.TASKVIEW_CLASSES.update(data)
|
||||
|
||||
register_taskview_class(r'^actions/CreateProject/?$', tasks.CreateProject)
|
||||
register_taskview_class(r'^actions/InviteUser/?$', tasks.InviteUser)
|
||||
register_taskview_class(r'^actions/ResetPassword/?$', tasks.ResetPassword)
|
||||
register_taskview_class(r'^actions/EditUser/?$', tasks.EditUser)
|
||||
|
||||
register_taskview_class(
|
||||
r'^openstack/users/?$', openstack.UserList)
|
||||
register_taskview_class(
|
||||
r'^openstack/users/(?P<user_id>\w+)/?$', openstack.UserDetail)
|
||||
register_taskview_class(
|
||||
r'^openstack/users/(?P<user_id>\w+)/roles/?$', openstack.UserRoles)
|
||||
register_taskview_class(
|
||||
r'^openstack/roles/?$', openstack.RoleList)
|
||||
register_taskview_class(
|
||||
r'^openstack/users/password-reset?$', openstack.UserResetPassword)
|
||||
register_taskview_class(
|
||||
r'^openstack/users/password-set?$', openstack.UserSetPassword)
|
||||
|
@ -163,7 +163,7 @@ class UserDetail(tasks.TaskView):
|
||||
|
||||
class UserRoles(tasks.TaskView):
|
||||
|
||||
default_action = 'EditUserRoles'
|
||||
default_actions = ['EditUserRoles', ]
|
||||
task_type = 'edit_roles'
|
||||
|
||||
@utils.mod_or_admin
|
||||
|
@ -28,15 +28,19 @@ from django.conf import settings
|
||||
class TaskView(APIViewWithLogger):
|
||||
"""
|
||||
Base class for api calls that start a Task.
|
||||
Until it is moved to settings, 'default_action' is a
|
||||
required hardcoded field.
|
||||
'default_actions' is a required hardcoded field.
|
||||
|
||||
The default_action is considered the primary action and
|
||||
will always run first. Additional actions are defined in
|
||||
the settings file and will run in the order supplied, but
|
||||
after the default_action.
|
||||
The default_actions are considered the primary actions and
|
||||
will always run first (in the given order). Additional actions
|
||||
are defined in the settings file and will run in the order supplied,
|
||||
but after the default_actions.
|
||||
|
||||
Default actions can be overridden in the settings file as well if
|
||||
needed.
|
||||
"""
|
||||
|
||||
default_actions = []
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
The get method will return a json listing the actions this
|
||||
@ -44,9 +48,11 @@ class TaskView(APIViewWithLogger):
|
||||
"""
|
||||
class_conf = settings.TASK_SETTINGS.get(self.task_type, {})
|
||||
|
||||
actions = [self.default_action, ]
|
||||
actions = (
|
||||
class_conf.get('default_actions', []) or
|
||||
self.default_actions[:])
|
||||
|
||||
actions += class_conf.get('actions', [])
|
||||
actions += class_conf.get('additional_actions', [])
|
||||
|
||||
required_fields = []
|
||||
|
||||
@ -70,9 +76,11 @@ class TaskView(APIViewWithLogger):
|
||||
|
||||
class_conf = settings.TASK_SETTINGS.get(self.task_type, {})
|
||||
|
||||
actions = [self.default_action, ]
|
||||
actions = (
|
||||
class_conf.get('default_actions', []) or
|
||||
self.default_actions[:])
|
||||
|
||||
actions += class_conf.get('actions', [])
|
||||
actions += class_conf.get('additional_actions', [])
|
||||
|
||||
action_list = []
|
||||
|
||||
@ -311,7 +319,7 @@ class CreateProject(TaskView):
|
||||
|
||||
task_type = "create_project"
|
||||
|
||||
default_action = "NewProject"
|
||||
default_actions = ["NewProject", ]
|
||||
|
||||
def post(self, request, format=None):
|
||||
"""
|
||||
@ -344,7 +352,7 @@ class InviteUser(TaskView):
|
||||
|
||||
task_type = "invite_user"
|
||||
|
||||
default_action = 'NewUser'
|
||||
default_actions = ['NewUser', ]
|
||||
|
||||
@utils.mod_or_admin
|
||||
def get(self, request):
|
||||
@ -387,7 +395,7 @@ class ResetPassword(TaskView):
|
||||
|
||||
task_type = "reset_password"
|
||||
|
||||
default_action = 'ResetUser'
|
||||
default_actions = ['ResetUser', ]
|
||||
|
||||
def post(self, request, format=None):
|
||||
"""
|
||||
@ -432,15 +440,17 @@ class EditUser(TaskView):
|
||||
|
||||
task_type = "edit_user"
|
||||
|
||||
default_action = 'EditUser'
|
||||
default_actions = ['EditUserRoles', ]
|
||||
|
||||
@utils.mod_or_admin
|
||||
def get(self, request):
|
||||
class_conf = settings.TASK_SETTINGS.get(self.task_type, {})
|
||||
|
||||
actions = [self.default_action, ]
|
||||
actions = (
|
||||
class_conf.get('default_actions', []) or
|
||||
self.default_actions[:])
|
||||
|
||||
actions += class_conf.get('actions', [])
|
||||
actions += class_conf.get('additional_actions', [])
|
||||
role_blacklist = class_conf.get('role_blacklist', [])
|
||||
|
||||
required_fields = []
|
||||
|
@ -14,8 +14,6 @@
|
||||
|
||||
from django.conf.urls import url
|
||||
from stacktask.api.v1 import views
|
||||
from stacktask.api.v1 import tasks
|
||||
from stacktask.api.v1 import openstack
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@ -28,23 +26,11 @@ urlpatterns = [
|
||||
url(r'^notifications/(?P<uuid>\w+)/?$',
|
||||
views.NotificationDetail.as_view()),
|
||||
url(r'^notifications/?$', views.NotificationList.as_view()),
|
||||
|
||||
url(r'^openstack/users/(?P<user_id>\w+)/roles/?$',
|
||||
openstack.UserRoles.as_view()),
|
||||
url(r'^openstack/users/(?P<user_id>\w+)/?$',
|
||||
openstack.UserDetail.as_view()),
|
||||
url(r'^openstack/users/password-reset?$',
|
||||
openstack.UserResetPassword.as_view()),
|
||||
url(r'^openstack/users/password-set?$',
|
||||
openstack.UserSetPassword.as_view()),
|
||||
url(r'^openstack/users/?$', openstack.UserList.as_view()),
|
||||
url(r'^openstack/roles/?$', openstack.RoleList.as_view()),
|
||||
]
|
||||
|
||||
if settings.SHOW_ACTION_ENDPOINTS:
|
||||
urlpatterns = urlpatterns + [
|
||||
url(r'^actions/CreateProject/?$', tasks.CreateProject.as_view()),
|
||||
url(r'^actions/InviteUser/?$', tasks.InviteUser.as_view()),
|
||||
url(r'^actions/ResetPassword/?$', tasks.ResetPassword.as_view()),
|
||||
url(r'^actions/EditUser/?$', tasks.EditUser.as_view()),
|
||||
]
|
||||
for active_view in settings.ACTIVE_TASKVIEWS:
|
||||
taskview = settings.TASKVIEW_CLASSES[active_view]
|
||||
|
||||
urlpatterns.append(
|
||||
url(taskview['url'], taskview['class'].as_view())
|
||||
)
|
||||
|
30
stacktask/exceptions.py
Normal file
30
stacktask/exceptions.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
class BaseException(Exception):
|
||||
"""An error occurred."""
|
||||
def __init__(self, message=None):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message or self.__class__.__doc__
|
||||
|
||||
|
||||
class TaskViewNotFound(BaseException):
|
||||
"""Attempting to setup TaskView that has not been registered."""
|
||||
|
||||
|
||||
class ActionNotFound(BaseException):
|
||||
"""Attempting to setup Action that has not been registered."""
|
@ -157,8 +157,6 @@ TOKEN_SUBMISSION_URL = CONFIG['TOKEN_SUBMISSION_URL']
|
||||
|
||||
TOKEN_EXPIRE_TIME = CONFIG['TOKEN_EXPIRE_TIME']
|
||||
|
||||
SHOW_ACTION_ENDPOINTS = CONFIG['SHOW_ACTION_ENDPOINTS']
|
||||
|
||||
TASK_SETTINGS = setup_task_settings(
|
||||
CONFIG['DEFAULT_TASK_SETTINGS'],
|
||||
CONFIG['TASK_SETTINGS'])
|
||||
@ -167,6 +165,22 @@ ACTION_SETTINGS = CONFIG['ACTION_SETTINGS']
|
||||
|
||||
ROLES_MAPPING = CONFIG['ROLES_MAPPING']
|
||||
|
||||
# Defaults for backwards compatibility.
|
||||
ACTIVE_TASKVIEWS = CONFIG.get(
|
||||
'ACTIVE_TASKVIEWS',
|
||||
[
|
||||
'UserRoles',
|
||||
'UserDetail',
|
||||
'UserResetPassword',
|
||||
'UserSetPassword',
|
||||
'UserList',
|
||||
'RoleList'
|
||||
])
|
||||
|
||||
# Dict of TaskViews and their url_paths.
|
||||
# - This is populated by registering taskviews.
|
||||
TASKVIEW_CLASSES = {}
|
||||
|
||||
# Dict of actions and their serializers.
|
||||
# - This is populated from the various model modules at startup:
|
||||
ACTION_CLASSES = {}
|
||||
|
@ -72,6 +72,19 @@ TOKEN_SUBMISSION_URL = 'http://localhost:8080/token/'
|
||||
|
||||
TOKEN_EXPIRE_TIME = 24
|
||||
|
||||
ACTIVE_TASKVIEWS = [
|
||||
'UserRoles',
|
||||
'UserDetail',
|
||||
'UserResetPassword',
|
||||
'UserSetPassword',
|
||||
'UserList',
|
||||
'RoleList',
|
||||
'CreateProject',
|
||||
'InviteUser',
|
||||
'ResetPassword',
|
||||
'EditUser',
|
||||
]
|
||||
|
||||
DEFAULT_TASK_SETTINGS = {
|
||||
'emails': {
|
||||
'token': {
|
||||
@ -182,6 +195,7 @@ conf_dict = {
|
||||
"USERNAME_IS_EMAIL": USERNAME_IS_EMAIL,
|
||||
"KEYSTONE": KEYSTONE,
|
||||
"DEFAULT_REGION": DEFAULT_REGION,
|
||||
"ACTIVE_TASKVIEWS": ACTIVE_TASKVIEWS,
|
||||
"DEFAULT_TASK_SETTINGS": DEFAULT_TASK_SETTINGS,
|
||||
"TASK_SETTINGS": TASK_SETTINGS,
|
||||
"ACTION_SETTINGS": ACTION_SETTINGS,
|
||||
|
Loading…
Reference in New Issue
Block a user