Skip to content

Commit 5522137

Browse files
fix: generation of web.conf for endpoints mentioned in endpointUrl (#1884)
**Issue number:** ADDON-82845 ### PR Type **What kind of change does this PR introduce?** * [x] Feature * [x] Bug Fix * [ ] Refactoring (no functional or API changes) * [ ] Documentation Update * [ ] Maintenance (dependency updates, CI, etc.) ## Summary ### Changes UCC will now able to generate stanzas for endpoint mentioned in `endpointUrl`. Also, earlier we were statically passing methods for non-ui rest handlers which is now being dynamically set as users wants them. Added an attribute name `capabilities` through which users can set required roles for their endpoints. ### User experience Users no longer need to create stanzas for the endpoints specified in `endpointUrl`, as these will now be generated automatically by UCC. Additionally, users can define `capabilities` for their endpoints, which will be reflected in `restmap.conf`. ## Checklist If an item doesn't apply to your changes, leave it unchecked. ### Review * [x] self-review - I have performed a self-review of this change according to the [development guidelines](https://splunk.github.io/addonfactory-ucc-generator/contributing/#development-guidelines) * [x] Changes are documented. The documentation is understandable, examples work [(more info)](https://splunk.github.io/addonfactory-ucc-generator/contributing/#documentation-guidelines) * [x] PR title and description follows the [contributing principles](https://splunk.github.io/addonfactory-ucc-generator/contributing/#pull-requests) * [ ] meeting - I have scheduled a meeting or recorded a demo to explain these changes (if there is a video, put a link below and in the ticket) ### Tests See [the testing doc](https://splunk.github.io/addonfactory-ucc-generator/contributing/#build-and-test). * [x] Unit - tests have been added/modified to cover the changes * [x] Smoke - tests have been added/modified to cover the changes * [ ] UI - tests have been added/modified to cover the changes * [x] coverage - I have checked the code coverage of my changes [(see more)](https://splunk.github.io/addonfactory-ucc-generator/contributing/#checking-the-code-coverage) **Demo/meeting:** *Reviewers are encouraged to request meetings or demos if any part of the change is unclear*
1 parent b27fd45 commit 5522137

File tree

27 files changed

+410
-353
lines changed

27 files changed

+410
-353
lines changed

docs/configurations/index.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ proxy configuration, and logging level configuration.
1212
| description | string | To provide a brief summary of an configuration page. |
1313
| [subDescription](../advanced/sub_description.md) | object | To provide broader description of an configuration page |
1414
| [tabs](#tabs-properties)<span class="required-asterisk">\*</span> | array | To specify a list of tab. |
15+
| capabilities | object | Specifies the capabilities required for the given methods. The provided capabilities will be generated in restmap.conf. For more information, refer to the Splunk documentation on [restmap.conf](https://docs.splunk.com/Documentation/Splunk/latest/Admin/restmapconf#restmap.conf.spec).|
1516

1617
### Tabs properties
1718

@@ -72,7 +73,11 @@ Currently available tab components:
7273
{
7374
"type": "loggingTab"
7475
}
75-
]
76+
],
77+
"capabilities": {
78+
"put": "admin_all_objects",
79+
"post": "list_storage_passwords"
80+
}
7681
}
7782
```
7883

docs/inputs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ provided, a dropdown field will appear on the Inputs page. In contrast, a button
2020
| [table](../table.md) | object | It displays input stanzas in a tabular format. |
2121
| groupsMenu | array | This property allows you to enable the [multi-level menu](./multilevel_menu.md) feature. |
2222
| [services](#services-properties)<span class="required-asterisk">\*</span> | array | It specifies a list of modular inputs. |
23+
| capabilities | object | Specifies the capabilities required for the given methods. The provided capabilities will be generated in restmap.conf. For more information, refer to the Splunk documentation on [restmap.conf](https://docs.splunk.com/Documentation/Splunk/latest/Admin/restmapconf#restmap.conf.spec).|
2324
| readonlyFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input cannot be edited from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. |
2425
| hideFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input is hidden from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. Check out an example below. |
2526
| useInputToggleConfirmation | boolean | When true, displays a confirmation modal before toggling an input's status between active and inactive. |

docs/options/rest_handlers.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ custom handlers developed by the user.
1313
"name": "StorageBuckets",
1414
"endpoint": "Splunk_TA_Example_buckets",
1515
"handlerType": "EAI",
16+
"resourcePresent": true,
1617
"registerHandler": {
1718
"file": "storage_buckets.py",
1819
"actions": ["list", "create", "remove", "edit"]
1920
},
21+
"capabilities": {
22+
"put": "admin_all_objects",
23+
"post": "list_storage_passwords"
24+
},
25+
"parentResource": "splunk_ta_uccexample",
2026
"requestParameters": {
2127
"create": {
2228
"param_name": {
@@ -57,6 +63,9 @@ OpenApi entries will be generated for the handler with the specified request and
5763
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
5864
| registerHandler | If it is specified, UCC will add proper lines to `web.conf` and `restmap.conf`. You need to specify handler file name and EAI actions. Example: `{"file": "handler.py", "actions" ["create", "list"]}` |
5965
| requestParameters | Request parameters for each action. The parameters are used in the OpenApi specification. See below. |
66+
| resourcePresent | Specifies whether the collection should expose its resources as well via web.conf or not. Default value is False.|
67+
| capabilities |Specifies the capabilities required for the given methods. The provided capabilities will be generated in restmap.conf. For more information, refer to the Splunk documentation on [restmap.conf](https://docs.splunk.com/Documentation/Splunk/latest/Admin/restmapconf#restmap.conf.spec).|
68+
| parentResource | Specifies parent Resource for the given endpoint, it will get added in the `match` attribute of restmap.conf. Default value is "/.|
6069
| responseParameters | Response parameters for each action. The parameters are used in the OpenApi specification. See below. |
6170

6271
`registerHandler` is needed to register the handler in the `web.conf` and `restmap.conf` files.

splunk_add_on_ucc_framework/commands/rest_builder/global_config_builder_schema.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,11 @@ def __init__(self, global_config: global_config_lib.GlobalConfig):
5757
self._settings_conf_file_names: List[str] = list()
5858
self._configs_conf_file_names: List[str] = list()
5959
self._oauth_conf_file_names: List[str] = list()
60-
self._endpoints: Dict[str, RestEndpointBuilder] = {}
60+
self._configuration_endpoints: Dict[str, RestEndpointBuilder] = {}
61+
self._inputs_endpoints: Dict[str, RestEndpointBuilder] = {}
6162
self._parse_builder_schema()
63+
self._endpoints: Dict[str, RestEndpointBuilder] = {}
64+
self._preserve_endpoint_dict()
6265

6366
@property
6467
def product(self) -> str:
@@ -84,10 +87,23 @@ def oauth_conf_file_names(self) -> List[str]:
8487
def endpoints(self) -> List[RestEndpointBuilder]:
8588
return list(self._endpoints.values())
8689

90+
@property
91+
def configuration_endpoints(self) -> List[RestEndpointBuilder]:
92+
return list(self._configuration_endpoints.values())
93+
94+
@property
95+
def inputs_endpoints(self) -> List[RestEndpointBuilder]:
96+
return list(self._inputs_endpoints.values())
97+
8798
@property
8899
def need_reload(self) -> bool:
89100
return False
90101

102+
def _preserve_endpoint_dict(self) -> Dict[str, RestEndpointBuilder]:
103+
self._endpoints.update(self._configuration_endpoints)
104+
self._endpoints.update(self._inputs_endpoints)
105+
return self._endpoints
106+
91107
def _parse_builder_schema(self) -> None:
92108
self._builder_configs()
93109
self._builder_settings()
@@ -110,7 +126,7 @@ def _builder_configs(self) -> None:
110126
log_stanza=log_details.get("name"),
111127
log_level_field=log_details.get("entity", [{}])[0].get("field"),
112128
)
113-
self._endpoints["oauth"] = oauth_endpoint
129+
self._configuration_endpoints["oauth"] = oauth_endpoint
114130
if oauth_endpoint.conf_name not in self._oauth_conf_file_names:
115131
self._oauth_conf_file_names.append(oauth_endpoint.conf_name)
116132

@@ -152,7 +168,7 @@ def _builder_configs(self) -> None:
152168
else:
153169
endpoint = SingleModelEndpointBuilder(**endpoint_params)
154170

155-
self._endpoints[name] = endpoint
171+
self._configuration_endpoints[name] = endpoint
156172
content = self._get_oauth_enitities(config["entity"])
157173
fields, special_fields = self._parse_fields(content)
158174
entity = SingleModelEntityBuilder(
@@ -177,7 +193,7 @@ def _builder_settings(self) -> None:
177193
rest_handler_class=REST_HANDLER_DEFAULT_CLASS,
178194
need_reload=self.need_reload,
179195
)
180-
self._endpoints["settings"] = endpoint
196+
self._configuration_endpoints["settings"] = endpoint
181197
for setting in self.global_config.settings:
182198
if setting.get("entity") is not None:
183199
content = self._get_oauth_enitities(setting["entity"])
@@ -212,7 +228,7 @@ def _builder_inputs(self) -> None:
212228
rest_handler_class=rest_handler_class,
213229
need_reload=self.need_reload,
214230
)
215-
self._endpoints[name] = single_model_endpoint
231+
self._inputs_endpoints[name] = single_model_endpoint
216232
content = self._get_oauth_enitities(input_item["entity"])
217233
fields, special_fields = self._parse_fields(content)
218234
single_model_entity = SingleModelEntityBuilder(
@@ -232,7 +248,7 @@ def _builder_inputs(self) -> None:
232248
rest_handler_module=rest_handler_module,
233249
rest_handler_class=rest_handler_class,
234250
)
235-
self._endpoints[name] = data_input_endpoint
251+
self._inputs_endpoints[name] = data_input_endpoint
236252
content = self._get_oauth_enitities(input_item["entity"])
237253
fields, special_fields = self._parse_fields(content)
238254
data_input_entity = DataInputEntityBuilder(

splunk_add_on_ucc_framework/commands/rest_builder/user_defined_rest_handlers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515
#
1616
from copy import deepcopy
17-
from dataclasses import dataclass
17+
from dataclasses import dataclass, field
1818
from typing import Dict, Any, Iterable, Optional, Set, List
1919

2020
from splunk_add_on_ucc_framework.commands.openapi_generator import oas
@@ -73,6 +73,9 @@ class EndpointRegistrationEntry:
7373
name: str
7474
rh_name: str
7575
actions_list: List[str]
76+
capabilities: Dict[str, str] = field(default_factory=dict)
77+
resourcePresent: bool = False
78+
parentResource: str = "/"
7679

7780
def actions(self) -> List[str]:
7881
"""
@@ -90,6 +93,9 @@ class RestHandlerConfig:
9093
name: str
9194
endpoint: str
9295
handlerType: str
96+
capabilities: Dict[str, str] = field(default_factory=dict)
97+
resourcePresent: bool = False
98+
parentResource: str = "/"
9399
registerHandler: Optional[Dict[str, Any]] = None
94100
requestParameters: Optional[Dict[str, Dict[str, Any]]] = None
95101
responseParameters: Optional[Dict[str, Dict[str, Any]]] = None
@@ -305,6 +311,9 @@ def endpoint_registration_entry(self) -> Optional[EndpointRegistrationEntry]:
305311
name=self.endpoint,
306312
rh_name=file,
307313
actions_list=self.registerHandler["actions"],
314+
resourcePresent=self.resourcePresent,
315+
parentResource=self.parentResource,
316+
capabilities=self.capabilities,
308317
)
309318

310319

splunk_add_on_ucc_framework/generators/conf_files/create_restmap_conf.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,33 @@ def __init__(
3737
super().__init__(global_config, input_dir, output_dir)
3838
self.conf_file = "restmap.conf"
3939
self.endpoints: List[Union[RestEndpointBuilder, EndpointRegistrationEntry]] = []
40+
self.configuration_endpoints: List[
41+
Union[RestEndpointBuilder, EndpointRegistrationEntry]
42+
] = []
43+
self.inputs_endpoints: List[
44+
Union[RestEndpointBuilder, EndpointRegistrationEntry]
45+
] = []
46+
self.custom_endpoints: List[
47+
Union[RestEndpointBuilder, EndpointRegistrationEntry]
48+
] = []
4049

4150
if global_config.has_pages():
42-
self.endpoints.extend(self._gc_schema.endpoints)
51+
self.configuration_endpoints.extend(self._gc_schema.configuration_endpoints)
52+
self.inputs_endpoints.extend(self._gc_schema.inputs_endpoints)
4353
self.namespace = self._gc_schema.namespace
44-
self.endpoints.extend(
45-
global_config.user_defined_handlers.endpoint_registration_entries
46-
)
54+
self.custom_endpoints.extend(
55+
global_config.user_defined_handlers.endpoint_registration_entries
56+
)
4757

48-
self.endpoint_names = ", ".join(sorted([ep.name for ep in self.endpoints]))
58+
self.configuration_endpoint_names = ", ".join(
59+
sorted([ep.name for ep in self.configuration_endpoints])
60+
)
61+
self.inputs_endpoint_names = ", ".join(
62+
sorted([ep.name for ep in self.inputs_endpoints])
63+
)
64+
self.endpoints.extend(self.configuration_endpoints)
65+
self.endpoints.extend(self.inputs_endpoints)
66+
self.endpoints.extend(self.custom_endpoints)
4967

5068
def generate(self) -> Optional[List[Dict[str, str]]]:
5169
if not self.endpoints:
@@ -57,8 +75,14 @@ def generate(self) -> Optional[List[Dict[str, str]]]:
5775
)
5876
rendered_content = self._template.render(
5977
endpoints=self.endpoints,
60-
endpoint_names=self.endpoint_names,
78+
configuration_endpoints=self.configuration_endpoints,
79+
inputs_endpoints=self.inputs_endpoints,
80+
configuration_endpoint_names=self.configuration_endpoint_names,
81+
inputs_endpoint_names=self.inputs_endpoint_names,
6182
namespace=self.namespace,
83+
configuration_capability=self._global_config.capabilities(config=True),
84+
input_capability=self._global_config.capabilities(inputs=True),
85+
custom_endpoints=self.custom_endpoints,
6286
)
6387
return [
6488
{

splunk_add_on_ucc_framework/generators/conf_files/create_web_conf.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
from splunk_add_on_ucc_framework.generators.file_generator import FileGenerator
2525
from splunk_add_on_ucc_framework.global_config import GlobalConfig
2626

27+
actions_to_methods = {
28+
"edit": ["POST", "GET"],
29+
"remove": ["DELETE"],
30+
"list": ["GET"],
31+
"create": ["POST"],
32+
}
33+
2734

2835
class WebConf(FileGenerator):
2936
__description__ = (
@@ -36,21 +43,38 @@ def __init__(
3643
) -> None:
3744
super().__init__(global_config, input_dir, output_dir)
3845

46+
def get_methods_for_actions(self, actions: List[str]) -> List[str]:
47+
methods = []
48+
for action in actions:
49+
methods.extend(actions_to_methods.get(action, []))
50+
51+
return sorted(list(set(methods)))
52+
3953
def generate(self) -> Optional[List[Dict[str, str]]]:
4054
if not self._global_config.has_pages():
4155
return None
4256

4357
endpoints: List[Union[RestEndpointBuilder, EndpointRegistrationEntry]] = []
58+
custom_endpoints: List[
59+
Union[RestEndpointBuilder, EndpointRegistrationEntry]
60+
] = []
4461
endpoints.extend(self._gc_schema.endpoints)
45-
endpoints.extend(
62+
methods = {}
63+
custom_endpoints.extend(
4664
self._global_config.user_defined_handlers.endpoint_registration_entries
4765
)
66+
67+
for endpoint in custom_endpoints:
68+
methods[endpoint.name] = self.get_methods_for_actions(endpoint.actions())
4869
conf_file = "web.conf"
4970

5071
file_path = self.get_file_output_path(["default", conf_file])
5172
rendered_content = self._render(
5273
"web_conf.template",
5374
endpoints=endpoints,
75+
custom_endpoints=custom_endpoints,
76+
methods=methods,
77+
endpointUrl=self._global_config.endpointUrl,
5478
)
5579
return [
5680
{

splunk_add_on_ucc_framework/global_config.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
#
1616
import functools
1717
import json
18-
from typing import Optional, Any, List, Dict
18+
from typing import Optional, Any, List, Dict, Set
1919
from dataclasses import dataclass, field, fields
20+
from urllib.parse import urlparse
2021

2122
import yaml
2223

@@ -166,6 +167,30 @@ def configuration(self) -> List[Any]:
166167
return self._content["pages"]["configuration"]["tabs"]
167168
return []
168169

170+
@property
171+
def endpointUrl(self) -> List[str]:
172+
def extract_urls(entities_list: List[Any]) -> Set[str]:
173+
urls = set()
174+
for content in entities_list:
175+
for entity in content.get("entity", []):
176+
if entity.get("type") in ("multipleSelect", "singleSelect"):
177+
endpoint = entity.get("options", {}).get("endpointUrl")
178+
# only include those endpoint which starts with data
179+
# the other ones are covered if they are defined in options->
180+
# TODO; If add-ons uses any other endpoints apart from data then add it here.
181+
if endpoint and endpoint.startswith("data"):
182+
urls.add(urlparse(endpoint).path)
183+
return urls
184+
185+
urls = set()
186+
if self.has_configuration():
187+
urls.update(extract_urls(self.configuration))
188+
189+
if self.has_inputs():
190+
urls.update(extract_urls(self.inputs))
191+
192+
return list(urls)
193+
169194
@property
170195
def pages(self) -> List[Any]:
171196
if "pages" in self._content:
@@ -233,6 +258,13 @@ def display_name(self) -> str:
233258
def version(self) -> str:
234259
return self.meta["version"]
235260

261+
def capabilities(self, **kwargs: bool) -> Dict[str, str]:
262+
if kwargs.get("config") and self.has_configuration():
263+
return self._content["pages"]["configuration"].get("capabilities", {})
264+
elif kwargs.get("inputs") and self.has_inputs():
265+
return self._content["pages"]["inputs"].get("capabilities", {})
266+
return {}
267+
236268
@property
237269
def ucc_version(self) -> str:
238270
return self.meta["_uccVersion"]

0 commit comments

Comments
 (0)