Skip to content

Commit 1339b00

Browse files
Add support for dynamic application configurations (#5855)
* Add application configuration service Signed-off-by: Tianle Huang <tianleh@amazon.com> * update API path name Signed-off-by: Tianle Huang <tianleh@amazon.com> * implement two APIs/interfaces Signed-off-by: Tianle Huang <tianleh@amazon.com> * expose get function for other plugins to use Signed-off-by: Tianle Huang <tianleh@amazon.com> * update interfaces Signed-off-by: Tianle Huang <tianleh@amazon.com> * implement the APIs and interfaces Signed-off-by: Tianle Huang <tianleh@amazon.com> * add license and jsdoc Signed-off-by: Tianle Huang <tianleh@amazon.com> * update docs Signed-off-by: Tianle Huang <tianleh@amazon.com> * add more docs Signed-off-by: Tianle Huang <tianleh@amazon.com> * update variable name Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove unnecessary dependency Signed-off-by: Tianle Huang <tianleh@amazon.com> * format readme Signed-off-by: Tianle Huang <tianleh@amazon.com> * use osd version Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove debugging info Signed-off-by: Tianle Huang <tianleh@amazon.com> * update logging Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove lint js Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove logs Signed-off-by: Tianle Huang <tianleh@amazon.com> * update name style Signed-off-by: Tianle Huang <tianleh@amazon.com> * update Signed-off-by: Tianle Huang <tianleh@amazon.com> * update function visibility and error function Signed-off-by: Tianle Huang <tianleh@amazon.com> * fix unit test failures Signed-off-by: Tianle Huang <tianleh@amazon.com> * add unit test Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove lint file Signed-off-by: Tianle Huang <tianleh@amazon.com> * add more tests Signed-off-by: Tianle Huang <tianleh@amazon.com> * add unit tests for routes Signed-off-by: Tianle Huang <tianleh@amazon.com> * add remaining unit tests Signed-off-by: Tianle Huang <tianleh@amazon.com> * add enabled to this plugin Signed-off-by: Tianle Huang <tianleh@amazon.com> * update readme to mention experimental Signed-off-by: Tianle Huang <tianleh@amazon.com> * update change log Signed-off-by: Tianle Huang <tianleh@amazon.com> * dummy commit to trigger workflow rerun Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove experimental Signed-off-by: Tianle Huang <tianleh@amazon.com> * add key to yml file Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove i18n Signed-off-by: Tianle Huang <tianleh@amazon.com> * remove lint rc Signed-off-by: Tianle Huang <tianleh@amazon.com> * update comment style Signed-off-by: Tianle Huang <tianleh@amazon.com> * add input validation Signed-off-by: Tianle Huang <tianleh@amazon.com> * update unit tests Signed-off-by: Tianle Huang <tianleh@amazon.com> * prevent multiple registration Signed-off-by: Tianle Huang <tianleh@amazon.com> * add return types Signed-off-by: Tianle Huang <tianleh@amazon.com> * update readme wording Signed-off-by: Tianle Huang <tianleh@amazon.com> * add unit test to the plugin class about double register Signed-off-by: Tianle Huang <tianleh@amazon.com> * move related ymls Signed-off-by: Tianle Huang <tianleh@amazon.com> * move validation to a function Signed-off-by: Tianle Huang <tianleh@amazon.com> * use trimmed versions Signed-off-by: Tianle Huang <tianleh@amazon.com> * reword changelog entry Signed-off-by: Tianle Huang <tianleh@amazon.com> * readability Signed-off-by: Tianle Huang <tianleh@amazon.com> * add back yml change Signed-off-by: Tianle Huang <tianleh@amazon.com> --------- Signed-off-by: Tianle Huang <tianleh@amazon.com> (cherry picked from commit 2c8d9d3) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> # Conflicts: # CHANGELOG.md
1 parent a9b54a6 commit 1339b00

20 files changed

+1463
-1
lines changed

config/opensearch_dashboards.yml

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
# dashboards. OpenSearch Dashboards creates a new index if the index doesn't already exist.
3030
#opensearchDashboards.index: ".opensearch_dashboards"
3131

32+
# OpenSearch Dashboards uses an index in OpenSearch to store dynamic configurations.
33+
# This shall be a different index from opensearchDashboards.index.
34+
# opensearchDashboards.configIndex: ".opensearch_dashboards_config"
35+
36+
# Set the value of this setting to true to enable plugin application config. By default it is disabled.
37+
# application_config.enabled: false
38+
3239
# The default application to load.
3340
#opensearchDashboards.defaultAppId: "home"
3441

src/core/server/mocks.ts

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export function pluginInitializerContextConfigMock<T>(config: T) {
7777
const globalConfig: SharedGlobalConfig = {
7878
opensearchDashboards: {
7979
index: '.opensearch_dashboards_tests',
80+
configIndex: '.opensearch_dashboards_config_tests',
8081
autocompleteTerminateAfter: duration(100000),
8182
autocompleteTimeout: duration(1000),
8283
},

src/core/server/opensearch_dashboards_config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const config = {
4848
schema: schema.object({
4949
enabled: schema.boolean({ defaultValue: true }),
5050
index: schema.string({ defaultValue: '.kibana' }),
51+
configIndex: schema.string({ defaultValue: '.opensearch_dashboards_config' }),
5152
autocompleteTerminateAfter: schema.duration({ defaultValue: 100000 }),
5253
autocompleteTimeout: schema.duration({ defaultValue: 1000 }),
5354
branding: schema.object({

src/core/server/plugins/plugin_context.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ describe('createPluginInitializerContext', () => {
9898
expect(configObject).toStrictEqual({
9999
opensearchDashboards: {
100100
index: '.kibana',
101+
configIndex: '.opensearch_dashboards_config',
101102
autocompleteTerminateAfter: duration(100000),
102103
autocompleteTimeout: duration(1000),
103104
},

src/core/server/plugins/types.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,12 @@ export interface Plugin<
287287

288288
export const SharedGlobalConfigKeys = {
289289
// We can add more if really needed
290-
opensearchDashboards: ['index', 'autocompleteTerminateAfter', 'autocompleteTimeout'] as const,
290+
opensearchDashboards: [
291+
'index',
292+
'configIndex',
293+
'autocompleteTerminateAfter',
294+
'autocompleteTimeout',
295+
] as const,
291296
opensearch: ['shardTimeout', 'requestTimeout', 'pingTimeout'] as const,
292297
path: ['data'] as const,
293298
savedObjects: ['maxImportPayloadBytes'] as const,

src/legacy/server/config/schema.js

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ export default () =>
227227
opensearchDashboards: Joi.object({
228228
enabled: Joi.boolean().default(true),
229229
index: Joi.string().default('.kibana'),
230+
configIndex: Joi.string().default('.opensearch_dashboards_config'),
230231
autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000),
231232
// TODO Also allow units here like in opensearch config once this is moved to the new platform
232233
autocompleteTimeout: Joi.number().integer().min(1).default(1000),
+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# ApplicationConfig Plugin
2+
3+
An OpenSearch Dashboards plugin for application configuration and a default implementation based on OpenSearch as storage.
4+
5+
---
6+
7+
## Introduction
8+
9+
This plugin introduces the support of dynamic application configurations as opposed to the existing static configuration in OSD YAML file `opensearch_dashboards.yml`. It stores the configuration in an index whose default name is `.opensearch_dashboards_config` and could be customized through the key `opensearchDashboards.configIndex` in OSD YAML file. Initially the new index does not exist. Only OSD users who need dynamic configurations will create it.
10+
11+
It also provides an interface `ConfigurationClient` for future extensions of external configuration clients. A default implementation based on OpenSearch as database is used.
12+
13+
This plugin is disabled by default.
14+
15+
## Configuration
16+
17+
OSD users who want to set up application configurations will first need to enable this plugin by the following line in OSD YML.
18+
19+
```
20+
application_config.enabled: true
21+
22+
```
23+
24+
Then they can perform configuration operations through CURL the OSD APIs.
25+
26+
(Note that the commands following could be first obtained from a copy as curl option from the network tab of a browser development tool and then replaced with the API names)
27+
28+
Below is the CURL command to view all configurations.
29+
30+
```
31+
curl '{osd endpoint}/api/appconfig' -X GET
32+
```
33+
34+
Below is the CURL command to view the configuration of an entity.
35+
36+
```
37+
curl '{osd endpoint}/api/appconfig/{entity}' -X GET
38+
39+
```
40+
41+
Below is the CURL command to update the configuration of an entity.
42+
43+
```
44+
curl '{osd endpoint}/api/appconfig/{entity}' -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' -H 'osd-xsrf: osd-fetch' -H 'Sec-Fetch-Dest: empty' --data-raw '{"newValue":"{new value}"}'
45+
```
46+
47+
Below is the CURL command to delete the configuration of an entity.
48+
49+
```
50+
curl '{osd endpoint}/api/appconfig/{entity}' -X DELETE -H 'osd-xsrf: osd-fetch' -H 'Sec-Fetch-Dest: empty'
51+
52+
```
53+
54+
55+
## External Configuration Clients
56+
57+
While a default OpenSearch based client is implemented, OSD users can use external configuration clients through an OSD plugin (outside OSD).
58+
59+
Let's call this plugin `MyConfigurationClientPlugin`.
60+
61+
First, this plugin will need to implement a class `MyConfigurationClient` based on interface `ConfigurationClient` defined in the `types.ts` under directory `src/plugins/application_config/server/types.ts`. Below are the functions inside the interface.
62+
63+
```
64+
getConfig(): Promise<Map<string, string>>;
65+
66+
getEntityConfig(entity: string): Promise<string>;
67+
68+
updateEntityConfig(entity: string, newValue: string): Promise<string>;
69+
70+
deleteEntityConfig(entity: string): Promise<string>;
71+
```
72+
73+
Second, this plugin needs to declare `applicationConfig` as its dependency by adding it to `requiredPlugins` in its own `opensearch_dashboards.json`.
74+
75+
Third, the plugin will define a new type called `AppPluginSetupDependencies` as follows in its own `types.ts`.
76+
77+
```
78+
export interface AppPluginSetupDependencies {
79+
applicationConfig: ApplicationConfigPluginSetup;
80+
}
81+
82+
```
83+
84+
Then the plugin will import the new type `AppPluginSetupDependencies` and add to its own setup input. Below is the skeleton of the class `MyConfigurationClientPlugin`.
85+
86+
```
87+
// MyConfigurationClientPlugin
88+
public setup(core: CoreSetup, { applicationConfig }: AppPluginSetupDependencies) {
89+
90+
...
91+
// The function createClient provides an instance of ConfigurationClient which
92+
// could have a underlying DynamoDB or Postgres implementation.
93+
const myConfigurationClient: ConfigurationClient = this.createClient();
94+
95+
applicationConfig.registerConfigurationClient(myConfigurationClient);
96+
...
97+
return {};
98+
}
99+
100+
```
101+
102+
## Onboarding Configurations
103+
104+
Since the APIs and interfaces can take an entity, a new use case to this plugin could just pass their entity into the parameters. There is no need to implement new APIs or interfaces. To programmatically call the functions in `ConfigurationClient` from a plugin (the caller plugin), below is the code example.
105+
106+
Similar to [section](#external-configuration-clients), a new type `AppPluginSetupDependencies` which encapsulates `ApplicationConfigPluginSetup` is needed. Then it can be imported into the `setup` function of the caller plugin. Then the caller plugin will have access to the `getConfigurationClient` and `registerConfigurationClient` exposed by `ApplicationConfigPluginSetup`.
107+
108+
## Development
109+
110+
See the [OpenSearch Dashboards contributing
111+
guide](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/CONTRIBUTING.md) for instructions
112+
setting up your development environment.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export const PLUGIN_ID = 'applicationConfig';
7+
export const PLUGIN_NAME = 'application_config';
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { schema, TypeOf } from '@osd/config-schema';
7+
8+
export const configSchema = schema.object({
9+
enabled: schema.boolean({ defaultValue: false }),
10+
});
11+
12+
export type ApplicationConfigSchema = TypeOf<typeof configSchema>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "applicationConfig",
3+
"version": "opensearchDashboards",
4+
"opensearchDashboardsVersion": "opensearchDashboards",
5+
"server": true,
6+
"ui": false,
7+
"requiredPlugins": [],
8+
"optionalPlugins": []
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { PluginConfigDescriptor, PluginInitializerContext } from '../../../core/server';
7+
import { ApplicationConfigSchema, configSchema } from '../config';
8+
import { ApplicationConfigPlugin } from './plugin';
9+
10+
/*
11+
This exports static code and TypeScript types,
12+
as well as, OpenSearch Dashboards Platform `plugin()` initializer.
13+
*/
14+
15+
export const config: PluginConfigDescriptor<ApplicationConfigSchema> = {
16+
schema: configSchema,
17+
};
18+
19+
export function plugin(initializerContext: PluginInitializerContext) {
20+
return new ApplicationConfigPlugin(initializerContext);
21+
}
22+
23+
export { ApplicationConfigPluginSetup, ApplicationConfigPluginStart } from './types';

0 commit comments

Comments
 (0)