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_SETTINGS:
|
||||||
EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
|
EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
|
||||||
|
|
||||||
# Application settings:
|
|
||||||
SHOW_ACTION_ENDPOINTS: False
|
|
||||||
|
|
||||||
# setting to control if user name and email are allowed
|
# setting to control if user name and email are allowed
|
||||||
# to have different values.
|
# to have different values.
|
||||||
USERNAME_IS_EMAIL: True
|
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
|
# time for the token to expire in hours
|
||||||
TOKEN_EXPIRE_TIME: 24
|
TOKEN_EXPIRE_TIME: 24
|
||||||
|
|
||||||
|
ACTIVE_TASKVIEWS:
|
||||||
|
- UserRoles
|
||||||
|
- UserDetail
|
||||||
|
- UserResetPassword
|
||||||
|
- UserSetPassword
|
||||||
|
- UserList
|
||||||
|
- RoleList
|
||||||
|
|
||||||
DEFAULT_TASK_SETTINGS:
|
DEFAULT_TASK_SETTINGS:
|
||||||
emails:
|
emails:
|
||||||
initial:
|
initial:
|
||||||
@ -110,10 +115,15 @@ DEFAULT_TASK_SETTINGS:
|
|||||||
# These are cascading overrides for the default settings:
|
# These are cascading overrides for the default settings:
|
||||||
TASK_SETTINGS:
|
TASK_SETTINGS:
|
||||||
create_project:
|
create_project:
|
||||||
# Additonal actions for views:
|
# You can override 'default_actions' if needed for given taskviews
|
||||||
# - The order of the actions matters. These will run after the
|
# The order of the actions is order of execution.
|
||||||
# default action, in the given order.
|
#
|
||||||
actions:
|
# default_actions:
|
||||||
|
# - NewProject
|
||||||
|
#
|
||||||
|
# Additonal actions for views
|
||||||
|
# These will run after the default actions, in the given order.
|
||||||
|
additional_actions:
|
||||||
- AddAdminToProject
|
- AddAdminToProject
|
||||||
- DefaultProjectResources
|
- DefaultProjectResources
|
||||||
notifications:
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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):
|
class UserRoles(tasks.TaskView):
|
||||||
|
|
||||||
default_action = 'EditUserRoles'
|
default_actions = ['EditUserRoles', ]
|
||||||
task_type = 'edit_roles'
|
task_type = 'edit_roles'
|
||||||
|
|
||||||
@utils.mod_or_admin
|
@utils.mod_or_admin
|
||||||
|
@ -28,15 +28,19 @@ from django.conf import settings
|
|||||||
class TaskView(APIViewWithLogger):
|
class TaskView(APIViewWithLogger):
|
||||||
"""
|
"""
|
||||||
Base class for api calls that start a Task.
|
Base class for api calls that start a Task.
|
||||||
Until it is moved to settings, 'default_action' is a
|
'default_actions' is a required hardcoded field.
|
||||||
required hardcoded field.
|
|
||||||
|
|
||||||
The default_action is considered the primary action and
|
The default_actions are considered the primary actions and
|
||||||
will always run first. Additional actions are defined in
|
will always run first (in the given order). Additional actions
|
||||||
the settings file and will run in the order supplied, but
|
are defined in the settings file and will run in the order supplied,
|
||||||
after the default_action.
|
but after the default_actions.
|
||||||
|
|
||||||
|
Default actions can be overridden in the settings file as well if
|
||||||
|
needed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
default_actions = []
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
The get method will return a json listing the actions this
|
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, {})
|
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 = []
|
required_fields = []
|
||||||
|
|
||||||
@ -70,9 +76,11 @@ class TaskView(APIViewWithLogger):
|
|||||||
|
|
||||||
class_conf = settings.TASK_SETTINGS.get(self.task_type, {})
|
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 = []
|
action_list = []
|
||||||
|
|
||||||
@ -311,7 +319,7 @@ class CreateProject(TaskView):
|
|||||||
|
|
||||||
task_type = "create_project"
|
task_type = "create_project"
|
||||||
|
|
||||||
default_action = "NewProject"
|
default_actions = ["NewProject", ]
|
||||||
|
|
||||||
def post(self, request, format=None):
|
def post(self, request, format=None):
|
||||||
"""
|
"""
|
||||||
@ -344,7 +352,7 @@ class InviteUser(TaskView):
|
|||||||
|
|
||||||
task_type = "invite_user"
|
task_type = "invite_user"
|
||||||
|
|
||||||
default_action = 'NewUser'
|
default_actions = ['NewUser', ]
|
||||||
|
|
||||||
@utils.mod_or_admin
|
@utils.mod_or_admin
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
@ -387,7 +395,7 @@ class ResetPassword(TaskView):
|
|||||||
|
|
||||||
task_type = "reset_password"
|
task_type = "reset_password"
|
||||||
|
|
||||||
default_action = 'ResetUser'
|
default_actions = ['ResetUser', ]
|
||||||
|
|
||||||
def post(self, request, format=None):
|
def post(self, request, format=None):
|
||||||
"""
|
"""
|
||||||
@ -432,15 +440,17 @@ class EditUser(TaskView):
|
|||||||
|
|
||||||
task_type = "edit_user"
|
task_type = "edit_user"
|
||||||
|
|
||||||
default_action = 'EditUser'
|
default_actions = ['EditUserRoles', ]
|
||||||
|
|
||||||
@utils.mod_or_admin
|
@utils.mod_or_admin
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
class_conf = settings.TASK_SETTINGS.get(self.task_type, {})
|
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', [])
|
role_blacklist = class_conf.get('role_blacklist', [])
|
||||||
|
|
||||||
required_fields = []
|
required_fields = []
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from stacktask.api.v1 import views
|
from stacktask.api.v1 import views
|
||||||
from stacktask.api.v1 import tasks
|
|
||||||
from stacktask.api.v1 import openstack
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
@ -28,23 +26,11 @@ urlpatterns = [
|
|||||||
url(r'^notifications/(?P<uuid>\w+)/?$',
|
url(r'^notifications/(?P<uuid>\w+)/?$',
|
||||||
views.NotificationDetail.as_view()),
|
views.NotificationDetail.as_view()),
|
||||||
url(r'^notifications/?$', views.NotificationList.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:
|
for active_view in settings.ACTIVE_TASKVIEWS:
|
||||||
urlpatterns = urlpatterns + [
|
taskview = settings.TASKVIEW_CLASSES[active_view]
|
||||||
url(r'^actions/CreateProject/?$', tasks.CreateProject.as_view()),
|
|
||||||
url(r'^actions/InviteUser/?$', tasks.InviteUser.as_view()),
|
urlpatterns.append(
|
||||||
url(r'^actions/ResetPassword/?$', tasks.ResetPassword.as_view()),
|
url(taskview['url'], taskview['class'].as_view())
|
||||||
url(r'^actions/EditUser/?$', tasks.EditUser.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']
|
TOKEN_EXPIRE_TIME = CONFIG['TOKEN_EXPIRE_TIME']
|
||||||
|
|
||||||
SHOW_ACTION_ENDPOINTS = CONFIG['SHOW_ACTION_ENDPOINTS']
|
|
||||||
|
|
||||||
TASK_SETTINGS = setup_task_settings(
|
TASK_SETTINGS = setup_task_settings(
|
||||||
CONFIG['DEFAULT_TASK_SETTINGS'],
|
CONFIG['DEFAULT_TASK_SETTINGS'],
|
||||||
CONFIG['TASK_SETTINGS'])
|
CONFIG['TASK_SETTINGS'])
|
||||||
@ -167,6 +165,22 @@ ACTION_SETTINGS = CONFIG['ACTION_SETTINGS']
|
|||||||
|
|
||||||
ROLES_MAPPING = CONFIG['ROLES_MAPPING']
|
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.
|
# Dict of actions and their serializers.
|
||||||
# - This is populated from the various model modules at startup:
|
# - This is populated from the various model modules at startup:
|
||||||
ACTION_CLASSES = {}
|
ACTION_CLASSES = {}
|
||||||
|
@ -72,6 +72,19 @@ TOKEN_SUBMISSION_URL = 'http://localhost:8080/token/'
|
|||||||
|
|
||||||
TOKEN_EXPIRE_TIME = 24
|
TOKEN_EXPIRE_TIME = 24
|
||||||
|
|
||||||
|
ACTIVE_TASKVIEWS = [
|
||||||
|
'UserRoles',
|
||||||
|
'UserDetail',
|
||||||
|
'UserResetPassword',
|
||||||
|
'UserSetPassword',
|
||||||
|
'UserList',
|
||||||
|
'RoleList',
|
||||||
|
'CreateProject',
|
||||||
|
'InviteUser',
|
||||||
|
'ResetPassword',
|
||||||
|
'EditUser',
|
||||||
|
]
|
||||||
|
|
||||||
DEFAULT_TASK_SETTINGS = {
|
DEFAULT_TASK_SETTINGS = {
|
||||||
'emails': {
|
'emails': {
|
||||||
'token': {
|
'token': {
|
||||||
@ -182,6 +195,7 @@ conf_dict = {
|
|||||||
"USERNAME_IS_EMAIL": USERNAME_IS_EMAIL,
|
"USERNAME_IS_EMAIL": USERNAME_IS_EMAIL,
|
||||||
"KEYSTONE": KEYSTONE,
|
"KEYSTONE": KEYSTONE,
|
||||||
"DEFAULT_REGION": DEFAULT_REGION,
|
"DEFAULT_REGION": DEFAULT_REGION,
|
||||||
|
"ACTIVE_TASKVIEWS": ACTIVE_TASKVIEWS,
|
||||||
"DEFAULT_TASK_SETTINGS": DEFAULT_TASK_SETTINGS,
|
"DEFAULT_TASK_SETTINGS": DEFAULT_TASK_SETTINGS,
|
||||||
"TASK_SETTINGS": TASK_SETTINGS,
|
"TASK_SETTINGS": TASK_SETTINGS,
|
||||||
"ACTION_SETTINGS": ACTION_SETTINGS,
|
"ACTION_SETTINGS": ACTION_SETTINGS,
|
||||||
|
Loading…
Reference in New Issue
Block a user