为 Adjutant 创建功能集

Adjutant 支持通过附加功能集引入新的动作、任务和委托 API。功能集是这些元素的集合,可能包含一些特定于功能集的额外配置。这允许公司特定或部署者特定的更改轻松地存在于核心服务之外,并在需要时简单地扩展核心服务。

一个这样的插件示例可以在这里找到(尽管它可能尚未采用新的“功能集”插件机制):https://github.com/catalyst-cloud/adjutant-odoo

一旦你拥有所有要包含在功能集中的动作、任务、委托 API 或通知处理程序,你就可以通过创建一个功能集类来注册它们

from adjutant.feature_set import BaseFeatureSet

from myplugin.actions import MyCustonAction
from myplugin.tasks import MyCustonTask
from myplugin.apis import MyCustonAPI
from myplugin.handlers import MyCustonNotificationHandler

class MyFeatureSet(BaseFeatureSet):

    actions = [
        MyCustonAction,
    ]

    tasks = [
        MyCustonTask,
    ]

    delegate_apis = [
        MyCustonAPI,
    ]

    notification_handlers = [
        MyCustonNotificationHandler,
    ]

然后将其添加到库入口点

adjutant.feature_sets =
    custom_thing = myplugin.features:MyFeatureSet

如果你的插件需要自定义配置,并且该配置应该在所有动作、任务、API 或通知处理程序中可访问且相同,那么你可以将配置注册到功能集本身

from confspirator import groups

....

class MyFeatureSet(BaseFeatureSet):

    .....

    config = groups.DynamicNameConfigGroup(
        children=[
            fields.StrConfig(
                'myconfig',
                help_text="Some custom config.",
                required=True,
                default="Stuff",
            ),
        ]
    )

可以通过 Adjutant 的配置在以下位置访问它:CONF.feature_sets.MyFeatureSet.myconfig

构建委托 API

新的委托 API 应该从 adjutant.api.v1.base.BaseDelegateAPI 继承

来自插件的新委托 API 可以通过使用相同的 URL 注册来有效地“覆盖”默认委托 API。但是,它必须具有不同的类名,并且必须从 ACTIVE_DELEGATE_APIS 中删除之前的委托 API。

可以在 adjutant.api.v1.openstack 中找到委托 API 的示例

最少情况下,它们可以如下所示

class MyCustomAPI(BaseDelegateAPI):

    url = r'^custom/mycoolstuff/?$'

    @utils.authenticated
    def post(self, request):
        self.task_manager.create_from_request('my_custom_task', request)

        return Response({'notes': ['task created']}, status=202)

可以使用在 adjutant.api.utils 中找到的 mod_or_admin、project_admin 和 admin 装饰器来限制访问。请求处理程序是相当标准的 django 视图处理程序,可以执行任何需要的代码。任务的附加信息应放置在 request.data 中。

你还可以通过设置 config_group 为委托 API 添加自定义配置

class MyCustomAPI(BaseDelegateAPI):

    url = r'^custom/mycoolstuff/?$'

    config_group = groups.DynamicNameConfigGroup(
        children=[
            fields.StrConfig(
                'myconfig',
                help_text="Some custom config.",
                required=True,
                default="Stuff",
            ),
        ]
    )

构建任务

任务必须从 adjutant.tasks.v1.base.BaseTask 派生。可以在 adjutant.tasks.v1 中找到任务示例

最少情况下,任务应该定义它们所需的字段

class My(MyPluginTask):
    task_type = "my_custom_task"
    default_actions = [
        "MyCustomAction",
    ]
    duplicate_policy = "cancel" # default is cancel

然后还有其他可选值可以设置

class My(MyPluginTask):
    ....

    # previous task_types
    deprecated_task_types = ['create_project']

    # config defaults for the task (used to generate default config):
    allow_auto_approve = True
    additional_actions = None
    token_expiry = None
    action_config = None
    email_config = None
    notification_config = None

构建动作

动作必须从 adjutant.actions.v1.base.BaseAction 派生。

序列化器可以继承自 rest_framework.serializers.Serializer,也可以继承自 adjutant.actions.v1.serializers 中的当前序列化器。

可以在 adjutant.actions.v1 中找到动作示例

最少情况下,动作应该定义它们所需的字段并实现 3 个函数

class MyCustomAction(BaseAction):

    required = [
        'user_id',
        'value1',
    ]

    serializer = MyCustomActionSerializer

    def _prepare(self):
        # Do some validation here
        pass

    def _approve(self):
        # Do some logic here
        self.action.task.cache['value'] = self.value1

    def _submit(self, token_data, keystone_user=None):
        # Do some logic here
        self.add_note("Submit action performed")

在动作任务缓存中设置的信息在电子邮件模板下可用:task.cache.value,动作数据在 action.ActionName.value 中可用。

如果需要发送令牌电子邮件,则动作还应实现

def _get_email(self):
    return self.keystone_user.email

如果动作不需要外部审批,则此函数应在预审批阶段运行

self.set_auto_approve(True)

如果动作需要令牌,则应在发布审批阶段设置

self.action.need_token = True
self.set_token_fields(["confirm"])

所有动作必须与序列化器配对,以进行基本数据结构检查,但还应在动作期间检查数据有效性。序列化器是 django-rest-framework 序列化器,但 adjutant.actions.v1.serializers 中还有两个基本序列化器可用,BaseUserNameSerializer 和 BaseUserIdSerializer。

动作所需的所有字段必须通过序列化器传递,否则它们将无法被动作访问。

示例

from adjutant.actions.v1.serializers import BaseUserIdSerializer
from rest_framework import serializers

class MyCustomActionSerializer(BaseUserIdSerializer):
    value_1 = serializers.CharField()

构建通知处理程序

通知处理程序也可以通过插件添加

from adjutant.notifications.models import BaseNotificationHandler
from adjutant.plugins import register_notification_handler

class NewNotificationHandler(BaseNotificationHandler):

    config_group = groups.DynamicNameConfigGroup(
        children=[
            fields.BoolConfig(
                "do_this_thing",
                help_text="Should we do the thing?",
                default=False,
            ),
        ]
    )

    def _notify(self, task, notification):
        conf = self.settings(task, notification)
        if conf.do_this_thing:
          # do something with the task and notification

然后你需要设置处理程序以默认方式用于任务,或用于特定任务

workflow:
    task_defaults:
        notifications:
            standard_handlers:
                - NewNotificationHandler
            standard_handler_settings:
                NewNotificationHandler:
                    do_this_thing: true
    tasks:
        some_task:
            notifications:
                standard_handlers: null
                error_handlers:
                    - NewNotificationHandler
                error_handler_settings:
                    NewNotificationHandler:
                        do_this_thing: true

使用身份管理器和 Openstack 客户端

身份管理器旨在取代对 Keystone 客户端的访问。它可以从 adjutant.actions.user_store.IdentityManager 导入。访问其他 Openstack 客户端的函数位于 adjutant.actions.openstack_clients 中。

未来将对此进行扩展,身份管理器本身也将变得可插拔。