Skip to content

Commit 31acf95

Browse files
authored
Add plugins endpoint to the REST API (#14280)
* add plugins endpoint * include access menu permission to plugin endpoint * apply suggestions from code review * fixup! apply suggestions from code review * refactor get plugins info * fixup! refactor get plugins info * fixup! fixup! refactor get plugins info * fixup! fixup! fixup! refactor get plugins info * added plugin in summary of changes * Remove menu access permission * remove menu_links and admin_views and add limit and offset to endpoint * fixup! remove menu_links and admin_views and add limit and offset to endpoint
1 parent e05ba51 commit 31acf95

File tree

7 files changed

+490
-26
lines changed

7 files changed

+490
-26
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
from airflow.api_connexion import security
18+
from airflow.api_connexion.parameters import check_limit, format_parameters
19+
from airflow.api_connexion.schemas.plugin_schema import PluginCollection, plugin_collection_schema
20+
from airflow.plugins_manager import get_plugin_info
21+
from airflow.security import permissions
22+
23+
24+
@security.requires_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_PLUGIN)])
25+
@format_parameters({'limit': check_limit})
26+
def get_plugins(limit, offset=0):
27+
"""Get plugins endpoint"""
28+
plugins_info = get_plugin_info()
29+
total_entries = len(plugins_info)
30+
plugins_info = plugins_info[offset:]
31+
plugins_info = plugins_info[:limit]
32+
return plugin_collection_schema.dump(PluginCollection(plugins=plugins_info, total_entries=total_entries))

airflow/api_connexion/openapi/v1.yaml

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ info:
132132
| Airflow version | Description |
133133
|-|-|
134134
| v2.0 | Initial release |
135+
| v2.0.2 | Added /plugins endpoint |
135136
136137
# Trying the API
137138
@@ -1380,7 +1381,28 @@ paths:
13801381
application/json:
13811382
schema:
13821383
$ref: '#/components/schemas/VersionInfo'
1383-
1384+
/plugins:
1385+
get:
1386+
summary: Get a list of loaded plugins
1387+
x-openapi-router-controller: airflow.api_connexion.endpoints.plugin_endpoint
1388+
operationId: get_plugins
1389+
tags: [Plugin]
1390+
parameters:
1391+
- $ref: '#/components/parameters/PageLimit'
1392+
- $ref: '#/components/parameters/PageOffset'
1393+
responses:
1394+
'200':
1395+
description: Success
1396+
content:
1397+
application/json:
1398+
schema:
1399+
$ref: '#/components/schemas/PluginCollection'
1400+
'401':
1401+
$ref: '#/components/responses/Unauthenticated'
1402+
'403':
1403+
$ref: '#/components/responses/PermissionDenied'
1404+
'404':
1405+
$ref: '#/components/responses/NotFound'
13841406

13851407
components:
13861408
# Reusable schemas (data models)
@@ -2064,6 +2086,81 @@ components:
20642086
type: array
20652087
items:
20662088
$ref: '#/components/schemas/Task'
2089+
# Plugin
2090+
PluginCollectionItem:
2091+
type: object
2092+
description: Plugin Item
2093+
properties:
2094+
number:
2095+
type: string
2096+
description: The plugin number
2097+
name:
2098+
type: string
2099+
description: The name of the plugin
2100+
hooks:
2101+
type: array
2102+
items:
2103+
type: string
2104+
nullable: true
2105+
description: The plugin hooks
2106+
executors:
2107+
type: array
2108+
items:
2109+
type: string
2110+
nullable: true
2111+
description: The plugin executors
2112+
macros:
2113+
type: array
2114+
items:
2115+
type: object
2116+
nullable: true
2117+
description: The plugin macros
2118+
flask_blueprints:
2119+
type: array
2120+
items:
2121+
type: object
2122+
nullable: true
2123+
description: The flask blueprints
2124+
appbuilder_views:
2125+
type: array
2126+
items:
2127+
type: object
2128+
nullable: true
2129+
description: The appuilder views
2130+
appbuilder_menu_items:
2131+
type: array
2132+
items:
2133+
type: object
2134+
nullable: true
2135+
description: The Flask Appbuilder menu items
2136+
global_operator_extra_links:
2137+
type: array
2138+
items:
2139+
type: object
2140+
nullable: true
2141+
description: The global operator extra links
2142+
operator_extra_links:
2143+
type: array
2144+
items:
2145+
type: object
2146+
nullable: true
2147+
description: Operator extra links
2148+
source:
2149+
type: string
2150+
description: The plugin source
2151+
nullable: true
2152+
2153+
PluginCollection:
2154+
type: object
2155+
description: Plugin Collection
2156+
allOf:
2157+
- type: object
2158+
properties:
2159+
plugins:
2160+
type: array
2161+
items:
2162+
$ref: '#/components/schemas/PluginCollectionItem'
2163+
- $ref: '#/components/schemas/CollectionInfo'
20672164

20682165
# Configuration
20692166
ConfigOption:
@@ -2910,6 +3007,7 @@ tags:
29103007
- name: TaskInstance
29113008
- name: Variable
29123009
- name: XCom
3010+
- name: Plugin
29133011

29143012

29153013
externalDocs:
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from typing import List, NamedTuple
19+
20+
from marshmallow import Schema, fields
21+
22+
23+
class PluginSchema(Schema):
24+
"""Plugin schema"""
25+
26+
number = fields.Int()
27+
name = fields.String()
28+
hooks = fields.List(fields.String())
29+
executors = fields.List(fields.String())
30+
macros = fields.List(fields.String())
31+
flask_blueprints = fields.List(fields.String())
32+
appbuilder_views = fields.List(fields.String())
33+
appbuilder_menu_items = fields.List(fields.Dict())
34+
global_operator_extra_links = fields.List(fields.String())
35+
operator_extra_links = fields.List(fields.String())
36+
source = fields.String()
37+
38+
39+
class PluginCollection(NamedTuple):
40+
"""Plugin List"""
41+
42+
plugins: List
43+
total_entries: int
44+
45+
46+
class PluginCollectionSchema(Schema):
47+
"""Plugin Collection List"""
48+
49+
plugins = fields.List(fields.Nested(PluginSchema))
50+
total_entries = fields.Int()
51+
52+
53+
plugin_schema = PluginSchema()
54+
plugin_collection_schema = PluginCollectionSchema()

airflow/cli/commands/plugins_command.py

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,9 @@
1919

2020
from airflow import plugins_manager
2121
from airflow.cli.simple_table import AirflowConsole
22-
from airflow.plugins_manager import PluginsDirectorySource
22+
from airflow.plugins_manager import PluginsDirectorySource, get_plugin_info
2323
from airflow.utils.cli import suppress_logs_and_warning
2424

25-
# list to maintain the order of items.
26-
PLUGINS_ATTRIBUTES_TO_DUMP = [
27-
"source",
28-
"hooks",
29-
"executors",
30-
"macros",
31-
"flask_blueprints",
32-
"appbuilder_views",
33-
"appbuilder_menu_items",
34-
"global_operator_extra_links",
35-
"operator_extra_links",
36-
]
37-
3825

3926
def _get_name(class_like_object) -> str:
4027
if isinstance(class_like_object, (str, PluginsDirectorySource)):
@@ -52,21 +39,11 @@ def _join_plugins_names(value: Union[List[Any], Any]) -> str:
5239
@suppress_logs_and_warning
5340
def dump_plugins(args):
5441
"""Dump plugins information"""
55-
plugins_manager.ensure_plugins_loaded()
56-
plugins_manager.integrate_macros_plugins()
57-
plugins_manager.integrate_executor_plugins()
58-
plugins_manager.initialize_extra_operators_links_plugins()
59-
plugins_manager.initialize_web_ui_plugins()
42+
plugins_info: List[Dict[str, str]] = get_plugin_info()
6043
if not plugins_manager.plugins:
6144
print("No plugins loaded")
6245
return
6346

64-
plugins_info: List[Dict[str, str]] = []
65-
for plugin in plugins_manager.plugins:
66-
info = {"name": plugin.name}
67-
info.update({n: getattr(plugin, n) for n in PLUGINS_ATTRIBUTES_TO_DUMP})
68-
plugins_info.append(info)
69-
7047
# Remove empty info
7148
if args.output == "table": # pylint: disable=too-many-nested-blocks
7249
# We can do plugins_info[0] as the element it will exist as there's

airflow/plugins_manager.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@
6363
Used by the DAG serialization code to only allow specific classes to be created
6464
during deserialization
6565
"""
66+
PLUGINS_ATTRIBUTES_TO_DUMP = {
67+
"hooks",
68+
"executors",
69+
"macros",
70+
"flask_blueprints",
71+
"appbuilder_views",
72+
"appbuilder_menu_items",
73+
"global_operator_extra_links",
74+
"operator_extra_links",
75+
"source",
76+
}
6677

6778

6879
class AirflowPluginSource:
@@ -427,3 +438,26 @@ def integrate_macros_plugins() -> None:
427438
# Register the newly created module on airflow.macros such that it
428439
# can be accessed when rendering templates.
429440
setattr(macros, plugin.name, macros_module)
441+
442+
443+
def get_plugin_info(attrs_to_dump: Optional[List[str]] = None) -> List[Dict[str, Any]]:
444+
"""
445+
Dump plugins attributes
446+
447+
:param attrs_to_dump: A list of plugin attributes to dump
448+
:type attrs_to_dump: List
449+
"""
450+
ensure_plugins_loaded()
451+
integrate_executor_plugins()
452+
integrate_macros_plugins()
453+
initialize_web_ui_plugins()
454+
initialize_extra_operators_links_plugins()
455+
if not attrs_to_dump:
456+
attrs_to_dump = PLUGINS_ATTRIBUTES_TO_DUMP
457+
plugins_info = []
458+
if plugins:
459+
for plugin in plugins:
460+
info = {"name": plugin.name}
461+
info.update({n: getattr(plugin, n) for n in attrs_to_dump})
462+
plugins_info.append(info)
463+
return plugins_info

0 commit comments

Comments
 (0)