mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* implements 'rename' and 'unset' deprecations * introduce usage of ConfigDeprecationProvider * adapt RawConfigService to only returns unmodified raw config * apply deprecations when accessing config * register legacy plugin deprecation in new platform * implements ConfigService#validate * add exemple config deprecation usage in testbed * documentation * export public config deprecation types * fix new test due to rebase * name ConfigDeprecationFactory * update generated doc * add tests for unset and move it to src/core/utils * add tests for renameFromRoot and unusedFromRoot * cast paths as any as get expects a fixed-length string array * use specific logger for deprecations * add additional test on renameFromRoot * update migration guide * migrate core deprecations to NP * add integration test * use same log context as legacy * remove old deprecation warnings integration tests, now covered in NP * migrates csp deprecation to NP * removes deprecationWarningMixin from legacy * remove legacy core deprecations * remove unused import * rename setupConfigSchemas to setupCoreConfig * update generated doc
This commit is contained in:
parent
ad5b013d73
commit
09e269508a
74 changed files with 2399 additions and 723 deletions
|
@ -9,5 +9,5 @@ Search for objects
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>>;
|
||||
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>>;
|
||||
```
|
||||
|
|
|
@ -20,7 +20,7 @@ export declare class SavedObjectsClient
|
|||
| [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | <code>(objects?: {</code><br/><code> id: string;</code><br/><code> type: string;</code><br/><code> }[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>></code> | Returns an array of objects by id |
|
||||
| [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <code><T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>></code> | Persists an object |
|
||||
| [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | <code>(type: string, id: string) => Promise<{}></code> | Deletes an object |
|
||||
| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <code><T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>></code> | Search for objects |
|
||||
| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <code><T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>></code> | Search for objects |
|
||||
| [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <code><T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>></code> | Fetches a single object |
|
||||
|
||||
## Methods
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md)
|
||||
|
||||
## ConfigDeprecation type
|
||||
|
||||
Configuration deprecation returned from [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md) that handles a single deprecation from the configuration.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type ConfigDeprecation = (config: Record<string, any>, fromPath: string, logger: ConfigDeprecationLogger) => Record<string, any>;
|
||||
```
|
||||
|
||||
## Remarks
|
||||
|
||||
This should only be manually implemented if [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) does not provide the proper helpers for a specific deprecation need.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md)
|
||||
|
||||
## ConfigDeprecationFactory interface
|
||||
|
||||
Provides helpers to generates the most commonly used [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) when invoking a [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md)<!-- -->.
|
||||
|
||||
See methods documentation for more detailed examples.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface ConfigDeprecationFactory
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [rename(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.rename.md) | Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. |
|
||||
| [renameFromRoot(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.<!-- -->This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. |
|
||||
| [unused(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unused.md) | Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. |
|
||||
| [unusedFromRoot(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unusedfromroot.md) | Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.<!-- -->This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
```typescript
|
||||
const provider: ConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
rename('oldKey', 'newKey'),
|
||||
unused('deprecatedKey'),
|
||||
]
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) > [rename](./kibana-plugin-server.configdeprecationfactory.rename.md)
|
||||
|
||||
## ConfigDeprecationFactory.rename() method
|
||||
|
||||
Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
rename(oldKey: string, newKey: string): ConfigDeprecation;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| oldKey | <code>string</code> | |
|
||||
| newKey | <code>string</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`ConfigDeprecation`
|
||||
|
||||
## Example
|
||||
|
||||
Rename 'myplugin.oldKey' to 'myplugin.newKey'
|
||||
|
||||
```typescript
|
||||
const provider: ConfigDeprecationProvider = ({ rename }) => [
|
||||
rename('oldKey', 'newKey'),
|
||||
]
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) > [renameFromRoot](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md)
|
||||
|
||||
## ConfigDeprecationFactory.renameFromRoot() method
|
||||
|
||||
Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.
|
||||
|
||||
This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| oldKey | <code>string</code> | |
|
||||
| newKey | <code>string</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`ConfigDeprecation`
|
||||
|
||||
## Example
|
||||
|
||||
Rename 'oldplugin.key' to 'newplugin.key'
|
||||
|
||||
```typescript
|
||||
const provider: ConfigDeprecationProvider = ({ renameFromRoot }) => [
|
||||
renameFromRoot('oldplugin.key', 'newplugin.key'),
|
||||
]
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) > [unused](./kibana-plugin-server.configdeprecationfactory.unused.md)
|
||||
|
||||
## ConfigDeprecationFactory.unused() method
|
||||
|
||||
Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
unused(unusedKey: string): ConfigDeprecation;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| unusedKey | <code>string</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`ConfigDeprecation`
|
||||
|
||||
## Example
|
||||
|
||||
Flags 'myplugin.deprecatedKey' as unused
|
||||
|
||||
```typescript
|
||||
const provider: ConfigDeprecationProvider = ({ unused }) => [
|
||||
unused('deprecatedKey'),
|
||||
]
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) > [unusedFromRoot](./kibana-plugin-server.configdeprecationfactory.unusedfromroot.md)
|
||||
|
||||
## ConfigDeprecationFactory.unusedFromRoot() method
|
||||
|
||||
Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.
|
||||
|
||||
This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
unusedFromRoot(unusedKey: string): ConfigDeprecation;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| unusedKey | <code>string</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`ConfigDeprecation`
|
||||
|
||||
## Example
|
||||
|
||||
Flags 'somepath.deprecatedProperty' as unused
|
||||
|
||||
```typescript
|
||||
const provider: ConfigDeprecationProvider = ({ unusedFromRoot }) => [
|
||||
unusedFromRoot('somepath.deprecatedProperty'),
|
||||
]
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecationLogger](./kibana-plugin-server.configdeprecationlogger.md)
|
||||
|
||||
## ConfigDeprecationLogger type
|
||||
|
||||
Logger interface used when invoking a [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type ConfigDeprecationLogger = (message: string) => void;
|
||||
```
|
|
@ -0,0 +1,28 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md)
|
||||
|
||||
## ConfigDeprecationProvider type
|
||||
|
||||
A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md)<!-- -->.
|
||||
|
||||
See [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) for more usage examples.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => ConfigDeprecation[];
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
```typescript
|
||||
const provider: ConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
rename('oldKey', 'newKey'),
|
||||
unused('deprecatedKey'),
|
||||
myCustomDeprecation,
|
||||
]
|
||||
|
||||
```
|
||||
|
|
@ -46,6 +46,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
|
||||
| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application.<!-- -->Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the <code>registerProvider</code> method.<!-- -->Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the <code>registerSwitcher</code> method.<!-- -->Refers to the methods documentation for complete description and examples. |
|
||||
| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md)<!-- -->. |
|
||||
| [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) | Provides helpers to generates the most commonly used [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) when invoking a [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md)<!-- -->.<!-- -->See methods documentation for more detailed examples. |
|
||||
| [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. |
|
||||
| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins <code>setup</code> method. |
|
||||
| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins <code>start</code> method. |
|
||||
|
@ -82,7 +83,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
|
||||
| [PackageInfo](./kibana-plugin-server.packageinfo.md) | |
|
||||
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. |
|
||||
| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. |
|
||||
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
|
||||
| [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. |
|
||||
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
|
||||
|
@ -152,6 +153,9 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [AuthResult](./kibana-plugin-server.authresult.md) | |
|
||||
| [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) |
|
||||
| [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) |
|
||||
| [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) | Configuration deprecation returned from [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md) that handles a single deprecation from the configuration. |
|
||||
| [ConfigDeprecationLogger](./kibana-plugin-server.configdeprecationlogger.md) | Logger interface used when invoking a [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) |
|
||||
| [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md) | A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md)<!-- -->.<!-- -->See [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) for more usage examples. |
|
||||
| [ConfigPath](./kibana-plugin-server.configpath.md) | |
|
||||
| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | |
|
||||
| [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. |
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) > [deprecations](./kibana-plugin-server.pluginconfigdescriptor.deprecations.md)
|
||||
|
||||
## PluginConfigDescriptor.deprecations property
|
||||
|
||||
Provider for the [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) to apply to the plugin configuration.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
deprecations?: ConfigDeprecationProvider;
|
||||
```
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## PluginConfigDescriptor interface
|
||||
|
||||
Describes a plugin configuration schema and capabilities.
|
||||
Describes a plugin configuration properties.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
@ -16,6 +16,7 @@ export interface PluginConfigDescriptor<T = any>
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [deprecations](./kibana-plugin-server.pluginconfigdescriptor.deprecations.md) | <code>ConfigDeprecationProvider</code> | Provider for the [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) to apply to the plugin configuration. |
|
||||
| [exposeToBrowser](./kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md) | <code>{</code><br/><code> [P in keyof T]?: boolean;</code><br/><code> }</code> | List of configuration properties that will be available on the client-side plugin. |
|
||||
| [schema](./kibana-plugin-server.pluginconfigdescriptor.schema.md) | <code>PluginConfigSchema<T></code> | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) |
|
||||
|
||||
|
@ -39,6 +40,10 @@ export const config: PluginConfigDescriptor<ConfigType> = {
|
|||
uiProp: true,
|
||||
},
|
||||
schema: configSchema,
|
||||
deprecations: ({ rename, unused }) => [
|
||||
rename('securityKey', 'secret'),
|
||||
unused('deprecatedProperty'),
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
|
|
@ -29,7 +29,6 @@ import { REPO_ROOT } from '@kbn/dev-utils';
|
|||
import Log from '../log';
|
||||
import Worker from './worker';
|
||||
import { Config } from '../../legacy/server/config/config';
|
||||
import { transformDeprecations } from '../../legacy/server/config/transform_deprecations';
|
||||
|
||||
process.env.kbnWorkerType = 'managr';
|
||||
|
||||
|
@ -37,7 +36,7 @@ export default class ClusterManager {
|
|||
static create(opts, settings = {}, basePathProxy) {
|
||||
return new ClusterManager(
|
||||
opts,
|
||||
Config.withDefaultSchema(transformDeprecations(settings)),
|
||||
Config.withDefaultSchema(settings),
|
||||
basePathProxy
|
||||
);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
- [UI Exports](#ui-exports)
|
||||
- [How to](#how-to)
|
||||
- [Configure plugin](#configure-plugin)
|
||||
- [Handle plugin configuration deprecations](#handle-plugin-config-deprecations)
|
||||
- [Mock new platform services in tests](#mock-new-platform-services-in-tests)
|
||||
- [Writing mocks for your plugin](#writing-mocks-for-your-plugin)
|
||||
- [Using mocks in your tests](#using-mocks-in-your-tests)
|
||||
|
@ -1220,13 +1221,20 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy';
|
|||
|
||||
In server code, `core` can be accessed from either `server.newPlatform` or `kbnServer.newPlatform`. There are not currently very many services available on the server-side:
|
||||
|
||||
| Legacy Platform | New Platform | Notes |
|
||||
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ |
|
||||
| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) |
|
||||
| `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | |
|
||||
| `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client |
|
||||
| `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client |
|
||||
| Legacy Platform | New Platform | Notes |
|
||||
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ |
|
||||
| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) |
|
||||
| `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | |
|
||||
| `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client |
|
||||
| `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client |
|
||||
| `xpackMainPlugin.info.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | |
|
||||
| `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactory`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | |
|
||||
| `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | |
|
||||
| `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | |
|
||||
| `server.savedObjects.getScopedSavedObjectsClient` | [`core.savedObjects.getScopedClient`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | |
|
||||
| `request.getSavedObjectsClient` | [`context.core.savedObjects.client`](/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md) | |
|
||||
| `kibana.Plugin.deprecations` | [Handle plugin configuration deprecations](#handle-plugin-config-deprecations) and [`PluginConfigDescriptor.deprecations`](docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md) | Deprecations from New Platform are not applied to legacy configuration |
|
||||
| `xpackMainPlugin.info.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | |
|
||||
|
||||
_See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-server.coresetup.md)_
|
||||
|
@ -1399,6 +1407,52 @@ export const config = {
|
|||
};
|
||||
```
|
||||
|
||||
#### Handle plugin configuration deprecations
|
||||
|
||||
If your plugin have deprecated properties, you can describe them using the `deprecations` config descriptor field.
|
||||
|
||||
The system is quite similar to the legacy plugin's deprecation management. The most important difference
|
||||
is that deprecations are managed on a per-plugin basis, meaning that you don't need to specify the whole
|
||||
property path, but use the relative path from your plugin's configuration root.
|
||||
|
||||
```typescript
|
||||
// my_plugin/server/index.ts
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { PluginConfigDescriptor } from 'kibana/server';
|
||||
|
||||
const configSchema = schema.object({
|
||||
newProperty: schema.string({ defaultValue: 'Some string' }),
|
||||
});
|
||||
|
||||
type ConfigType = TypeOf<typeof configSchema>;
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||||
schema: configSchema,
|
||||
deprecations: ({ rename, unused }) => [
|
||||
rename('oldProperty', 'newProperty'),
|
||||
unused('someUnusedProperty'),
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
In some cases, accessing the whole configuration for deprecations is necessary. For these edge cases,
|
||||
`renameFromRoot` and `unusedFromRoot` are also accessible when declaring deprecations.
|
||||
|
||||
```typescript
|
||||
// my_plugin/server/index.ts
|
||||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||||
schema: configSchema,
|
||||
deprecations: ({ renameFromRoot, unusedFromRoot }) => [
|
||||
renameFromRoot('oldplugin.property', 'myplugin.property'),
|
||||
unusedFromRoot('oldplugin.deprecated'),
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
Note that deprecations registered in new platform's plugins are not applied to the legacy configuration.
|
||||
During migration, if you still need the deprecations to be effective in the legacy plugin, you need to declare them in
|
||||
both plugin definitions.
|
||||
|
||||
### Mock new platform services in tests
|
||||
|
||||
#### Writing mocks for your plugin
|
||||
|
|
|
@ -886,7 +886,7 @@ export class SavedObjectsClient {
|
|||
bulkUpdate<T extends SavedObjectAttributes>(objects?: SavedObjectsBulkUpdateObject[]): Promise<SavedObjectsBatchResponse<SavedObjectAttributes>>;
|
||||
create: <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>>;
|
||||
delete: (type: string, id: string) => Promise<{}>;
|
||||
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectsFindOptions, "search" | "filter" | "type" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>>;
|
||||
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectsFindOptions, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>>;
|
||||
get: <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>>;
|
||||
update<T extends SavedObjectAttributes>(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise<SimpleSavedObject<T>>;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
import chalk from 'chalk';
|
||||
import { isMaster } from 'cluster';
|
||||
import { CliArgs, Env, RawConfigService } from './config';
|
||||
import { LegacyObjectToConfigAdapter } from './legacy';
|
||||
import { Root } from './root';
|
||||
import { CriticalError } from './errors';
|
||||
|
||||
|
@ -62,14 +61,10 @@ export async function bootstrap({
|
|||
isDevClusterMaster: isMaster && cliArgs.dev && features.isClusterModeSupported,
|
||||
});
|
||||
|
||||
const rawConfigService = new RawConfigService(
|
||||
env.configs,
|
||||
rawConfig => new LegacyObjectToConfigAdapter(applyConfigOverrides(rawConfig))
|
||||
);
|
||||
|
||||
const rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
|
||||
rawConfigService.loadConfig();
|
||||
|
||||
const root = new Root(rawConfigService.getConfig$(), env, onRootShutdown);
|
||||
const root = new Root(rawConfigService, env, onRootShutdown);
|
||||
|
||||
process.on('SIGHUP', () => reloadLoggingConfig());
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ const createConfigServiceMock = ({
|
|||
getUnusedPaths: jest.fn(),
|
||||
isEnabledAtPath: jest.fn(),
|
||||
setSchema: jest.fn(),
|
||||
addDeprecationProvider: jest.fn(),
|
||||
validate: jest.fn(),
|
||||
};
|
||||
mocked.atPath.mockReturnValue(new BehaviorSubject(atPath));
|
||||
mocked.getConfig$.mockReturnValue(new BehaviorSubject(new ObjectToConfigAdapter(getConfig$)));
|
||||
|
|
|
@ -19,3 +19,8 @@
|
|||
|
||||
export const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
|
||||
jest.mock('../../../../package.json', () => mockPackage);
|
||||
|
||||
export const mockApplyDeprecations = jest.fn((config, deprecations, log) => config);
|
||||
jest.mock('./deprecation/apply_deprecations', () => ({
|
||||
applyDeprecations: mockApplyDeprecations,
|
||||
}));
|
||||
|
|
|
@ -20,13 +20,14 @@
|
|||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { first, take } from 'rxjs/operators';
|
||||
|
||||
import { mockPackage } from './config_service.test.mocks';
|
||||
import { mockPackage, mockApplyDeprecations } from './config_service.test.mocks';
|
||||
import { rawConfigServiceMock } from './raw_config_service.mock';
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { ConfigService, Env, ObjectToConfigAdapter } from '.';
|
||||
import { ConfigService, Env } from '.';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { getEnvOptions } from './__mocks__/env';
|
||||
|
||||
|
@ -34,9 +35,12 @@ const emptyArgv = getEnvOptions();
|
|||
const defaultEnv = new Env('/kibana', emptyArgv);
|
||||
const logger = loggingServiceMock.create();
|
||||
|
||||
const getRawConfigProvider = (rawConfig: Record<string, any>) =>
|
||||
rawConfigServiceMock.create({ rawConfig });
|
||||
|
||||
test('returns config at path as observable', async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 'foo' }));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfig = getRawConfigProvider({ key: 'foo' });
|
||||
const configService = new ConfigService(rawConfig, defaultEnv, logger);
|
||||
const stringSchema = schema.string();
|
||||
await configService.setSchema('key', stringSchema);
|
||||
|
||||
|
@ -48,21 +52,36 @@ test('returns config at path as observable', async () => {
|
|||
});
|
||||
|
||||
test('throws if config at path does not match schema', async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 123 }));
|
||||
const rawConfig = getRawConfigProvider({ key: 123 });
|
||||
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const configService = new ConfigService(rawConfig, defaultEnv, logger);
|
||||
await configService.setSchema('key', schema.string());
|
||||
|
||||
await expect(
|
||||
configService.setSchema('key', schema.string())
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"[config validation of [key]]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
const valuesReceived: any[] = [];
|
||||
await configService
|
||||
.atPath('key')
|
||||
.pipe(take(1))
|
||||
.subscribe(
|
||||
value => {
|
||||
valuesReceived.push(value);
|
||||
},
|
||||
error => {
|
||||
valuesReceived.push(error);
|
||||
}
|
||||
);
|
||||
|
||||
await expect(valuesReceived).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
[Error: [config validation of [key]]: expected value of type [string] but got [number]],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('re-validate config when updated', async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 'value' }));
|
||||
const rawConfig$ = new BehaviorSubject<Record<string, any>>({ key: 'value' });
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig$ });
|
||||
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
configService.setSchema('key', schema.string());
|
||||
|
||||
const valuesReceived: any[] = [];
|
||||
|
@ -75,19 +94,19 @@ test('re-validate config when updated', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
config$.next(new ObjectToConfigAdapter({ key: 123 }));
|
||||
rawConfig$.next({ key: 123 });
|
||||
|
||||
await expect(valuesReceived).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"value",
|
||||
[Error: [config validation of [key]]: expected value of type [string] but got [number]],
|
||||
]
|
||||
Array [
|
||||
"value",
|
||||
[Error: [config validation of [key]]: expected value of type [string] but got [number]],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("returns undefined if fetching optional config at a path that doesn't exist", async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({}));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfig = getRawConfigProvider({});
|
||||
const configService = new ConfigService(rawConfig, defaultEnv, logger);
|
||||
|
||||
const value$ = configService.optionalAtPath('unique-name');
|
||||
const value = await value$.pipe(first()).toPromise();
|
||||
|
@ -96,8 +115,8 @@ test("returns undefined if fetching optional config at a path that doesn't exist
|
|||
});
|
||||
|
||||
test('returns observable config at optional path if it exists', async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ value: 'bar' }));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfig = getRawConfigProvider({ value: 'bar' });
|
||||
const configService = new ConfigService(rawConfig, defaultEnv, logger);
|
||||
await configService.setSchema('value', schema.string());
|
||||
|
||||
const value$ = configService.optionalAtPath('value');
|
||||
|
@ -107,8 +126,10 @@ test('returns observable config at optional path if it exists', async () => {
|
|||
});
|
||||
|
||||
test("does not push new configs when reloading if config at path hasn't changed", async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 'value' }));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfig$ = new BehaviorSubject<Record<string, any>>({ key: 'value' });
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig$ });
|
||||
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
await configService.setSchema('key', schema.string());
|
||||
|
||||
const valuesReceived: any[] = [];
|
||||
|
@ -116,14 +137,16 @@ test("does not push new configs when reloading if config at path hasn't changed"
|
|||
valuesReceived.push(value);
|
||||
});
|
||||
|
||||
config$.next(new ObjectToConfigAdapter({ key: 'value' }));
|
||||
rawConfig$.next({ key: 'value' });
|
||||
|
||||
expect(valuesReceived).toEqual(['value']);
|
||||
});
|
||||
|
||||
test('pushes new config when reloading and config at path has changed', async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 'value' }));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfig$ = new BehaviorSubject<Record<string, any>>({ key: 'value' });
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig$ });
|
||||
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
await configService.setSchema('key', schema.string());
|
||||
|
||||
const valuesReceived: any[] = [];
|
||||
|
@ -131,14 +154,14 @@ test('pushes new config when reloading and config at path has changed', async ()
|
|||
valuesReceived.push(value);
|
||||
});
|
||||
|
||||
config$.next(new ObjectToConfigAdapter({ key: 'new value' }));
|
||||
rawConfig$.next({ key: 'new value' });
|
||||
|
||||
expect(valuesReceived).toEqual(['value', 'new value']);
|
||||
});
|
||||
|
||||
test("throws error if 'schema' is not defined for a key", async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 'value' }));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: { key: 'value' } });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
const configs = configService.atPath('key');
|
||||
|
||||
|
@ -148,8 +171,8 @@ test("throws error if 'schema' is not defined for a key", async () => {
|
|||
});
|
||||
|
||||
test("throws error if 'setSchema' called several times for the same key", async () => {
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 'value' }));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: { key: 'value' } });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
const addSchema = async () => await configService.setSchema('key', schema.string());
|
||||
await addSchema();
|
||||
await expect(addSchema()).rejects.toMatchInlineSnapshot(
|
||||
|
@ -157,6 +180,32 @@ test("throws error if 'setSchema' called several times for the same key", async
|
|||
);
|
||||
});
|
||||
|
||||
test('flags schema paths as handled when registering a schema', async () => {
|
||||
const rawConfigProvider = rawConfigServiceMock.create({
|
||||
rawConfig: {
|
||||
service: {
|
||||
string: 'str',
|
||||
number: 42,
|
||||
},
|
||||
},
|
||||
});
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
await configService.setSchema(
|
||||
'service',
|
||||
schema.object({
|
||||
string: schema.string(),
|
||||
number: schema.number(),
|
||||
})
|
||||
);
|
||||
|
||||
expect(await configService.getUsedPaths()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"service.string",
|
||||
"service.number",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('tracks unhandled paths', async () => {
|
||||
const initialConfig = {
|
||||
bar: {
|
||||
|
@ -178,8 +227,8 @@ test('tracks unhandled paths', async () => {
|
|||
},
|
||||
};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
configService.atPath('foo');
|
||||
configService.atPath(['bar', 'deep2']);
|
||||
|
@ -201,8 +250,8 @@ test('correctly passes context', async () => {
|
|||
};
|
||||
|
||||
const env = new Env('/kibana', getEnvOptions());
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: { foo: {} } });
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ foo: {} }));
|
||||
const schemaDefinition = schema.object({
|
||||
branchRef: schema.string({
|
||||
defaultValue: schema.contextRef('branch'),
|
||||
|
@ -219,7 +268,7 @@ test('correctly passes context', async () => {
|
|||
defaultValue: schema.contextRef('version'),
|
||||
}),
|
||||
});
|
||||
const configService = new ConfigService(config$, env, logger);
|
||||
const configService = new ConfigService(rawConfigProvider, env, logger);
|
||||
await configService.setSchema('foo', schemaDefinition);
|
||||
const value$ = configService.atPath('foo');
|
||||
|
||||
|
@ -234,8 +283,8 @@ test('handles enabled path, but only marks the enabled path as used', async () =
|
|||
},
|
||||
};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
const isEnabled = await configService.isEnabledAtPath('pid');
|
||||
expect(isEnabled).toBe(true);
|
||||
|
@ -252,8 +301,8 @@ test('handles enabled path when path is array', async () => {
|
|||
},
|
||||
};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
const isEnabled = await configService.isEnabledAtPath(['pid']);
|
||||
expect(isEnabled).toBe(true);
|
||||
|
@ -270,8 +319,8 @@ test('handles disabled path and marks config as used', async () => {
|
|||
},
|
||||
};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
const isEnabled = await configService.isEnabledAtPath('pid');
|
||||
expect(isEnabled).toBe(false);
|
||||
|
@ -287,9 +336,9 @@ test('does not throw if schema does not define "enabled" schema', async () => {
|
|||
},
|
||||
};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
expect(
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
await expect(
|
||||
configService.setSchema(
|
||||
'pid',
|
||||
schema.object({
|
||||
|
@ -310,8 +359,8 @@ test('does not throw if schema does not define "enabled" schema', async () => {
|
|||
test('treats config as enabled if config path is not present in config', async () => {
|
||||
const initialConfig = {};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
const isEnabled = await configService.isEnabledAtPath('pid');
|
||||
expect(isEnabled).toBe(true);
|
||||
|
@ -327,8 +376,8 @@ test('read "enabled" even if its schema is not present', async () => {
|
|||
},
|
||||
};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
const isEnabled = await configService.isEnabledAtPath('foo');
|
||||
expect(isEnabled).toBe(true);
|
||||
|
@ -337,8 +386,8 @@ test('read "enabled" even if its schema is not present', async () => {
|
|||
test('allows plugins to specify "enabled" flag via validation schema', async () => {
|
||||
const initialConfig = {};
|
||||
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig));
|
||||
const configService = new ConfigService(config$, defaultEnv, logger);
|
||||
const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig });
|
||||
const configService = new ConfigService(rawConfigProvider, defaultEnv, logger);
|
||||
|
||||
await configService.setSchema(
|
||||
'foo',
|
||||
|
@ -361,3 +410,49 @@ test('allows plugins to specify "enabled" flag via validation schema', async ()
|
|||
|
||||
expect(await configService.isEnabledAtPath('baz')).toBe(true);
|
||||
});
|
||||
|
||||
test('does not throw during validation is every schema is valid', async () => {
|
||||
const rawConfig = getRawConfigProvider({ stringKey: 'foo', numberKey: 42 });
|
||||
|
||||
const configService = new ConfigService(rawConfig, defaultEnv, logger);
|
||||
await configService.setSchema('stringKey', schema.string());
|
||||
await configService.setSchema('numberKey', schema.number());
|
||||
|
||||
await expect(configService.validate()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test('throws during validation is any schema is invalid', async () => {
|
||||
const rawConfig = getRawConfigProvider({ stringKey: 123, numberKey: 42 });
|
||||
|
||||
const configService = new ConfigService(rawConfig, defaultEnv, logger);
|
||||
await configService.setSchema('stringKey', schema.string());
|
||||
await configService.setSchema('numberKey', schema.number());
|
||||
|
||||
await expect(configService.validate()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"[config validation of [stringKey]]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('logs deprecation warning during validation', async () => {
|
||||
const rawConfig = getRawConfigProvider({});
|
||||
const configService = new ConfigService(rawConfig, defaultEnv, logger);
|
||||
|
||||
mockApplyDeprecations.mockImplementationOnce((config, deprecations, log) => {
|
||||
log('some deprecation message');
|
||||
log('another deprecation message');
|
||||
return config;
|
||||
});
|
||||
|
||||
loggingServiceMock.clear(logger);
|
||||
await configService.validate();
|
||||
expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"some deprecation message",
|
||||
],
|
||||
Array [
|
||||
"another deprecation message",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -19,12 +19,20 @@
|
|||
|
||||
import { Type } from '@kbn/config-schema';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, first, map } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, first, map, shareReplay, take } from 'rxjs/operators';
|
||||
|
||||
import { Config, ConfigPath, Env } from '.';
|
||||
import { Logger, LoggerFactory } from '../logging';
|
||||
import { hasConfigPathIntersection } from './config';
|
||||
import { RawConfigurationProvider } from './raw_config_service';
|
||||
import {
|
||||
applyDeprecations,
|
||||
ConfigDeprecationWithContext,
|
||||
ConfigDeprecationProvider,
|
||||
configDeprecationFactory,
|
||||
} from './deprecation';
|
||||
import { LegacyObjectToConfigAdapter } from '../legacy/config';
|
||||
|
||||
/** @internal */
|
||||
export type IConfigService = PublicMethodsOf<ConfigService>;
|
||||
|
@ -32,6 +40,9 @@ export type IConfigService = PublicMethodsOf<ConfigService>;
|
|||
/** @internal */
|
||||
export class ConfigService {
|
||||
private readonly log: Logger;
|
||||
private readonly deprecationLog: Logger;
|
||||
|
||||
private readonly config$: Observable<Config>;
|
||||
|
||||
/**
|
||||
* Whenever a config if read at a path, we mark that path as 'handled'. We can
|
||||
|
@ -39,13 +50,23 @@ export class ConfigService {
|
|||
*/
|
||||
private readonly handledPaths: ConfigPath[] = [];
|
||||
private readonly schemas = new Map<string, Type<unknown>>();
|
||||
private readonly deprecations = new BehaviorSubject<ConfigDeprecationWithContext[]>([]);
|
||||
|
||||
constructor(
|
||||
private readonly config$: Observable<Config>,
|
||||
private readonly rawConfigProvider: RawConfigurationProvider,
|
||||
private readonly env: Env,
|
||||
logger: LoggerFactory
|
||||
) {
|
||||
this.log = logger.get('config');
|
||||
this.deprecationLog = logger.get('config', 'deprecation');
|
||||
|
||||
this.config$ = combineLatest([this.rawConfigProvider.getConfig$(), this.deprecations]).pipe(
|
||||
map(([rawConfig, deprecations]) => {
|
||||
const migrated = applyDeprecations(rawConfig, deprecations);
|
||||
return new LegacyObjectToConfigAdapter(migrated);
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,10 +79,37 @@ export class ConfigService {
|
|||
}
|
||||
|
||||
this.schemas.set(namespace, schema);
|
||||
this.markAsHandled(path);
|
||||
}
|
||||
|
||||
await this.validateConfig(path)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
/**
|
||||
* Register a {@link ConfigDeprecationProvider} to be used when validating and migrating the configuration
|
||||
*/
|
||||
public addDeprecationProvider(path: ConfigPath, provider: ConfigDeprecationProvider) {
|
||||
const flatPath = pathToString(path);
|
||||
this.deprecations.next([
|
||||
...this.deprecations.value,
|
||||
...provider(configDeprecationFactory).map(deprecation => ({
|
||||
deprecation,
|
||||
path: flatPath,
|
||||
})),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the whole configuration and log the deprecation warnings.
|
||||
*
|
||||
* This must be done after every schemas and deprecation providers have been registered.
|
||||
*/
|
||||
public async validate() {
|
||||
const namespaces = [...this.schemas.keys()];
|
||||
for (let i = 0; i < namespaces.length; i++) {
|
||||
await this.validateConfigAtPath(namespaces[i])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
await this.logDeprecation();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +127,7 @@ export class ConfigService {
|
|||
* @param path - The path to the desired subset of the config.
|
||||
*/
|
||||
public atPath<TSchema>(path: ConfigPath) {
|
||||
return this.validateConfig(path) as Observable<TSchema>;
|
||||
return this.validateConfigAtPath(path) as Observable<TSchema>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +140,7 @@ export class ConfigService {
|
|||
return this.getDistinctConfig(path).pipe(
|
||||
map(config => {
|
||||
if (config === undefined) return undefined;
|
||||
return this.validate(path, config) as TSchema;
|
||||
return this.validateAtPath(path, config) as TSchema;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -148,7 +196,21 @@ export class ConfigService {
|
|||
return config.getFlattenedPaths().filter(path => isPathHandled(path, handledPaths));
|
||||
}
|
||||
|
||||
private validate(path: ConfigPath, config: Record<string, unknown>) {
|
||||
private async logDeprecation() {
|
||||
const rawConfig = await this.rawConfigProvider
|
||||
.getConfig$()
|
||||
.pipe(take(1))
|
||||
.toPromise();
|
||||
const deprecations = await this.deprecations.pipe(take(1)).toPromise();
|
||||
const deprecationMessages: string[] = [];
|
||||
const logger = (msg: string) => deprecationMessages.push(msg);
|
||||
applyDeprecations(rawConfig, deprecations, logger);
|
||||
deprecationMessages.forEach(msg => {
|
||||
this.deprecationLog.warn(msg);
|
||||
});
|
||||
}
|
||||
|
||||
private validateAtPath(path: ConfigPath, config: Record<string, unknown>) {
|
||||
const namespace = pathToString(path);
|
||||
const schema = this.schemas.get(namespace);
|
||||
if (!schema) {
|
||||
|
@ -165,8 +227,8 @@ export class ConfigService {
|
|||
);
|
||||
}
|
||||
|
||||
private validateConfig(path: ConfigPath) {
|
||||
return this.getDistinctConfig(path).pipe(map(config => this.validate(path, config)));
|
||||
private validateConfigAtPath(path: ConfigPath) {
|
||||
return this.getDistinctConfig(path).pipe(map(config => this.validateAtPath(path, config)));
|
||||
}
|
||||
|
||||
private getDistinctConfig(path: ConfigPath) {
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { applyDeprecations } from './apply_deprecations';
|
||||
import { ConfigDeprecation, ConfigDeprecationWithContext } from './types';
|
||||
import { configDeprecationFactory as deprecations } from './deprecation_factory';
|
||||
|
||||
const wrapHandler = (
|
||||
handler: ConfigDeprecation,
|
||||
path: string = ''
|
||||
): ConfigDeprecationWithContext => ({
|
||||
deprecation: handler,
|
||||
path,
|
||||
});
|
||||
|
||||
describe('applyDeprecations', () => {
|
||||
it('calls all deprecations handlers once', () => {
|
||||
const handlerA = jest.fn();
|
||||
const handlerB = jest.fn();
|
||||
const handlerC = jest.fn();
|
||||
applyDeprecations(
|
||||
{},
|
||||
[handlerA, handlerB, handlerC].map(h => wrapHandler(h))
|
||||
);
|
||||
expect(handlerA).toHaveBeenCalledTimes(1);
|
||||
expect(handlerB).toHaveBeenCalledTimes(1);
|
||||
expect(handlerC).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls handlers with correct arguments', () => {
|
||||
const logger = () => undefined;
|
||||
const initialConfig = { foo: 'bar', deprecated: 'deprecated' };
|
||||
const alteredConfig = { foo: 'bar' };
|
||||
|
||||
const handlerA = jest.fn().mockReturnValue(alteredConfig);
|
||||
const handlerB = jest.fn().mockImplementation(conf => conf);
|
||||
|
||||
applyDeprecations(
|
||||
initialConfig,
|
||||
[wrapHandler(handlerA, 'pathA'), wrapHandler(handlerB, 'pathB')],
|
||||
logger
|
||||
);
|
||||
|
||||
expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', logger);
|
||||
expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', logger);
|
||||
});
|
||||
|
||||
it('returns the migrated config', () => {
|
||||
const initialConfig = { foo: 'bar', deprecated: 'deprecated', renamed: 'renamed' };
|
||||
|
||||
const migrated = applyDeprecations(initialConfig, [
|
||||
wrapHandler(deprecations.unused('deprecated')),
|
||||
wrapHandler(deprecations.rename('renamed', 'newname')),
|
||||
]);
|
||||
|
||||
expect(migrated).toEqual({ foo: 'bar', newname: 'renamed' });
|
||||
});
|
||||
|
||||
it('does not alter the initial config', () => {
|
||||
const initialConfig = { foo: 'bar', deprecated: 'deprecated' };
|
||||
|
||||
const migrated = applyDeprecations(initialConfig, [
|
||||
wrapHandler(deprecations.unused('deprecated')),
|
||||
]);
|
||||
|
||||
expect(initialConfig).toEqual({ foo: 'bar', deprecated: 'deprecated' });
|
||||
expect(migrated).toEqual({ foo: 'bar' });
|
||||
});
|
||||
});
|
40
src/core/server/config/deprecation/apply_deprecations.ts
Normal file
40
src/core/server/config/deprecation/apply_deprecations.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { ConfigDeprecationWithContext, ConfigDeprecationLogger } from './types';
|
||||
|
||||
const noopLogger = (msg: string) => undefined;
|
||||
|
||||
/**
|
||||
* Applies deprecations on given configuration and logs any deprecation warning using provided logger.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const applyDeprecations = (
|
||||
config: Record<string, any>,
|
||||
deprecations: ConfigDeprecationWithContext[],
|
||||
logger: ConfigDeprecationLogger = noopLogger
|
||||
) => {
|
||||
let processed = cloneDeep(config);
|
||||
deprecations.forEach(({ deprecation, path }) => {
|
||||
processed = deprecation(processed, path, logger);
|
||||
});
|
||||
return processed;
|
||||
};
|
211
src/core/server/config/deprecation/core_deprecations.test.ts
Normal file
211
src/core/server/config/deprecation/core_deprecations.test.ts
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { coreDeprecationProvider } from './core_deprecations';
|
||||
import { configDeprecationFactory } from './deprecation_factory';
|
||||
import { applyDeprecations } from './apply_deprecations';
|
||||
|
||||
const initialEnv = { ...process.env };
|
||||
|
||||
const applyCoreDeprecations = (settings: Record<string, any> = {}) => {
|
||||
const deprecations = coreDeprecationProvider(configDeprecationFactory);
|
||||
const deprecationMessages: string[] = [];
|
||||
const migrated = applyDeprecations(
|
||||
settings,
|
||||
deprecations.map(deprecation => ({
|
||||
deprecation,
|
||||
path: '',
|
||||
})),
|
||||
msg => deprecationMessages.push(msg)
|
||||
);
|
||||
return {
|
||||
messages: deprecationMessages,
|
||||
migrated,
|
||||
};
|
||||
};
|
||||
|
||||
describe('core deprecations', () => {
|
||||
beforeEach(() => {
|
||||
process.env = { ...initialEnv };
|
||||
});
|
||||
|
||||
describe('configPath', () => {
|
||||
it('logs a warning if CONFIG_PATH environ variable is set', () => {
|
||||
process.env.CONFIG_PATH = 'somepath';
|
||||
const { messages } = applyCoreDeprecations();
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Environment variable CONFIG_PATH is deprecated. It has been replaced with KIBANA_PATH_CONF pointing to a config folder",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not log a warning if CONFIG_PATH environ variable is unset', () => {
|
||||
delete process.env.CONFIG_PATH;
|
||||
const { messages } = applyCoreDeprecations();
|
||||
expect(messages).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataPath', () => {
|
||||
it('logs a warning if DATA_PATH environ variable is set', () => {
|
||||
process.env.DATA_PATH = 'somepath';
|
||||
const { messages } = applyCoreDeprecations();
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Environment variable \\"DATA_PATH\\" will be removed. It has been replaced with kibana.yml setting \\"path.data\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not log a warning if DATA_PATH environ variable is unset', () => {
|
||||
delete process.env.DATA_PATH;
|
||||
const { messages } = applyCoreDeprecations();
|
||||
expect(messages).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rewriteBasePath', () => {
|
||||
it('logs a warning is server.basePath is set and server.rewriteBasePath is not', () => {
|
||||
const { messages } = applyCoreDeprecations({
|
||||
server: {
|
||||
basePath: 'foo',
|
||||
},
|
||||
});
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana will expect that all requests start with server.basePath rather than expecting you to rewrite the requests in your reverse proxy. Set server.rewriteBasePath to false to preserve the current behavior and silence this warning.",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not log a warning if both server.basePath and server.rewriteBasePath are unset', () => {
|
||||
const { messages } = applyCoreDeprecations({
|
||||
server: {},
|
||||
});
|
||||
expect(messages).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not log a warning if both server.basePath and server.rewriteBasePath are set', () => {
|
||||
const { messages } = applyCoreDeprecations({
|
||||
server: {
|
||||
basePath: 'foo',
|
||||
rewriteBasePath: true,
|
||||
},
|
||||
});
|
||||
expect(messages).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cspRulesDeprecation', () => {
|
||||
describe('with nonce source', () => {
|
||||
it('logs a warning', () => {
|
||||
const settings = {
|
||||
csp: {
|
||||
rules: [`script-src 'self' 'nonce-{nonce}'`],
|
||||
},
|
||||
};
|
||||
const { messages } = applyCoreDeprecations(settings);
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in script-src",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('replaces a nonce', () => {
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src 'nonce-{nonce}'`] } }).migrated.csp
|
||||
.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src 'unsafe-eval' 'nonce-{nonce}'`] } })
|
||||
.migrated.csp.rules
|
||||
).toEqual([`script-src 'unsafe-eval' 'self'`]);
|
||||
});
|
||||
|
||||
it('removes a quoted nonce', () => {
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src 'self' 'nonce-{nonce}'`] } }).migrated
|
||||
.csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src 'nonce-{nonce}' 'self'`] } }).migrated
|
||||
.csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
});
|
||||
|
||||
it('removes a non-quoted nonce', () => {
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src 'self' nonce-{nonce}`] } }).migrated
|
||||
.csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src nonce-{nonce} 'self'`] } }).migrated
|
||||
.csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
});
|
||||
|
||||
it('removes a strange nonce', () => {
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src 'self' blah-{nonce}-wow`] } }).migrated
|
||||
.csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
});
|
||||
|
||||
it('removes multiple nonces', () => {
|
||||
expect(
|
||||
applyCoreDeprecations({
|
||||
csp: {
|
||||
rules: [
|
||||
`script-src 'nonce-{nonce}' 'self' blah-{nonce}-wow`,
|
||||
`style-src 'nonce-{nonce}' 'self'`,
|
||||
],
|
||||
},
|
||||
}).migrated.csp.rules
|
||||
).toEqual([`script-src 'self'`, `style-src 'self'`]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without self source', () => {
|
||||
it('logs a warning', () => {
|
||||
const { messages } = applyCoreDeprecations({
|
||||
csp: { rules: [`script-src 'unsafe-eval'`] },
|
||||
});
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"csp.rules must contain the 'self' source. Automatically adding to script-src.",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('adds self', () => {
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`script-src 'unsafe-eval'`] } }).migrated.csp.rules
|
||||
).toEqual([`script-src 'unsafe-eval' 'self'`]);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not add self to other policies', () => {
|
||||
expect(
|
||||
applyCoreDeprecations({ csp: { rules: [`worker-src blob:`] } }).migrated.csp.rules
|
||||
).toEqual([`worker-src blob:`]);
|
||||
});
|
||||
});
|
||||
});
|
114
src/core/server/config/deprecation/core_deprecations.ts
Normal file
114
src/core/server/config/deprecation/core_deprecations.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { has, get } from 'lodash';
|
||||
import { ConfigDeprecationProvider, ConfigDeprecation } from './types';
|
||||
|
||||
const configPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
|
||||
if (has(process.env, 'CONFIG_PATH')) {
|
||||
log(
|
||||
`Environment variable CONFIG_PATH is deprecated. It has been replaced with KIBANA_PATH_CONF pointing to a config folder`
|
||||
);
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
|
||||
if (has(process.env, 'DATA_PATH')) {
|
||||
log(
|
||||
`Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`
|
||||
);
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
|
||||
if (has(settings, 'server.basePath') && !has(settings, 'server.rewriteBasePath')) {
|
||||
log(
|
||||
'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' +
|
||||
'will expect that all requests start with server.basePath rather than expecting you to rewrite ' +
|
||||
'the requests in your reverse proxy. Set server.rewriteBasePath to false to preserve the ' +
|
||||
'current behavior and silence this warning.'
|
||||
);
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
|
||||
const NONCE_STRING = `{nonce}`;
|
||||
// Policies that should include the 'self' source
|
||||
const SELF_POLICIES = Object.freeze(['script-src', 'style-src']);
|
||||
const SELF_STRING = `'self'`;
|
||||
|
||||
const rules: string[] = get(settings, 'csp.rules');
|
||||
if (rules) {
|
||||
const parsed = new Map(
|
||||
rules.map(ruleStr => {
|
||||
const parts = ruleStr.split(/\s+/);
|
||||
return [parts[0], parts.slice(1)];
|
||||
})
|
||||
);
|
||||
|
||||
settings.csp.rules = [...parsed].map(([policy, sourceList]) => {
|
||||
if (sourceList.find(source => source.includes(NONCE_STRING))) {
|
||||
log(`csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`);
|
||||
sourceList = sourceList.filter(source => !source.includes(NONCE_STRING));
|
||||
|
||||
// Add 'self' if not present
|
||||
if (!sourceList.find(source => source.includes(SELF_STRING))) {
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
SELF_POLICIES.includes(policy) &&
|
||||
!sourceList.find(source => source.includes(SELF_STRING))
|
||||
) {
|
||||
log(`csp.rules must contain the 'self' source. Automatically adding to ${policy}.`);
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
|
||||
return `${policy} ${sourceList.join(' ')}`.trim();
|
||||
});
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
export const coreDeprecationProvider: ConfigDeprecationProvider = ({
|
||||
unusedFromRoot,
|
||||
renameFromRoot,
|
||||
}) => [
|
||||
unusedFromRoot('savedObjects.indexCheckTimeout'),
|
||||
unusedFromRoot('server.xsrf.token'),
|
||||
unusedFromRoot('uiSettings.enabled'),
|
||||
renameFromRoot('optimize.lazy', 'optimize.watch'),
|
||||
renameFromRoot('optimize.lazyPort', 'optimize.watchPort'),
|
||||
renameFromRoot('optimize.lazyHost', 'optimize.watchHost'),
|
||||
renameFromRoot('optimize.lazyPrebuild', 'optimize.watchPrebuild'),
|
||||
renameFromRoot('optimize.lazyProxyTimeout', 'optimize.watchProxyTimeout'),
|
||||
renameFromRoot('xpack.telemetry.enabled', 'telemetry.enabled'),
|
||||
renameFromRoot('xpack.telemetry.config', 'telemetry.config'),
|
||||
renameFromRoot('xpack.telemetry.banner', 'telemetry.banner'),
|
||||
renameFromRoot('xpack.telemetry.url', 'telemetry.url'),
|
||||
configPathDeprecation,
|
||||
dataPathDeprecation,
|
||||
rewriteBasePathDeprecation,
|
||||
cspRulesDeprecation,
|
||||
];
|
379
src/core/server/config/deprecation/deprecation_factory.test.ts
Normal file
379
src/core/server/config/deprecation/deprecation_factory.test.ts
Normal file
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { ConfigDeprecationLogger } from './types';
|
||||
import { configDeprecationFactory } from './deprecation_factory';
|
||||
|
||||
describe('DeprecationFactory', () => {
|
||||
const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory;
|
||||
|
||||
let deprecationMessages: string[];
|
||||
const logger: ConfigDeprecationLogger = msg => deprecationMessages.push(msg);
|
||||
|
||||
beforeEach(() => {
|
||||
deprecationMessages = [];
|
||||
});
|
||||
|
||||
describe('rename', () => {
|
||||
it('moves the property to rename and logs a warning if old property exist and new one does not', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
deprecated: 'toberenamed',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'toberenamed',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
it('does not alter config and does not log if old property is not present', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
new: 'new',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = rename('deprecated', 'new')(rawConfig, 'myplugin', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
new: 'new',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages.length).toEqual(0);
|
||||
});
|
||||
it('handles nested keys', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
oldsection: {
|
||||
deprecated: 'toberenamed',
|
||||
},
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = rename('oldsection.deprecated', 'newsection.renamed')(
|
||||
rawConfig,
|
||||
'myplugin',
|
||||
logger
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
oldsection: {},
|
||||
newsection: {
|
||||
renamed: 'toberenamed',
|
||||
},
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"myplugin.oldsection.deprecated\\" is deprecated and has been replaced by \\"myplugin.newsection.renamed\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
it('remove the old property but does not overrides the new one if they both exist, and logs a specific message', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
deprecated: 'deprecated',
|
||||
renamed: 'renamed',
|
||||
},
|
||||
};
|
||||
const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'renamed',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renameFromRoot', () => {
|
||||
it('moves the property from root and logs a warning if old property exist and new one does not', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
deprecated: 'toberenamed',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
logger
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'toberenamed',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('can move a property to a different namespace', () => {
|
||||
const rawConfig = {
|
||||
oldplugin: {
|
||||
deprecated: 'toberenamed',
|
||||
valid: 'valid',
|
||||
},
|
||||
newplugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('oldplugin.deprecated', 'newplugin.renamed')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
logger
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
oldplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
newplugin: {
|
||||
renamed: 'toberenamed',
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"oldplugin.deprecated\\" is deprecated and has been replaced by \\"newplugin.renamed\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not alter config and does not log if old property is not present', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
new: 'new',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('myplugin.deprecated', 'myplugin.new')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
logger
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
new: 'new',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('remove the old property but does not overrides the new one if they both exist, and logs a specific message', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
deprecated: 'deprecated',
|
||||
renamed: 'renamed',
|
||||
},
|
||||
};
|
||||
const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')(
|
||||
rawConfig,
|
||||
'does-not-matter',
|
||||
logger
|
||||
);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
renamed: 'renamed',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unused', () => {
|
||||
it('removes the unused property from the config and logs a warning is present', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
deprecated: 'deprecated',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unused('deprecated')(rawConfig, 'myplugin', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"myplugin.deprecated is deprecated and is no longer used",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles deeply nested keys', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
section: {
|
||||
deprecated: 'deprecated',
|
||||
},
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unused('section.deprecated')(rawConfig, 'myplugin', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
section: {},
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"myplugin.section.deprecated is deprecated and is no longer used",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not alter config and does not log if unused property is not present', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unused('deprecated')(rawConfig, 'myplugin', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unusedFromRoot', () => {
|
||||
it('removes the unused property from the root config and logs a warning is present', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
deprecated: 'deprecated',
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unusedFromRoot('myplugin.deprecated')(rawConfig, 'does-not-matter', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"myplugin.deprecated is deprecated and is no longer used",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not alter config and does not log if unused property is not present', () => {
|
||||
const rawConfig = {
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
};
|
||||
const processed = unusedFromRoot('myplugin.deprecated')(rawConfig, 'does-not-matter', logger);
|
||||
expect(processed).toEqual({
|
||||
myplugin: {
|
||||
valid: 'valid',
|
||||
},
|
||||
someOtherPlugin: {
|
||||
property: 'value',
|
||||
},
|
||||
});
|
||||
expect(deprecationMessages.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
95
src/core/server/config/deprecation/deprecation_factory.ts
Normal file
95
src/core/server/config/deprecation/deprecation_factory.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { get, set } from 'lodash';
|
||||
import { ConfigDeprecation, ConfigDeprecationLogger, ConfigDeprecationFactory } from './types';
|
||||
import { unset } from '../../../utils';
|
||||
|
||||
const _rename = (
|
||||
config: Record<string, any>,
|
||||
rootPath: string,
|
||||
log: ConfigDeprecationLogger,
|
||||
oldKey: string,
|
||||
newKey: string
|
||||
) => {
|
||||
const fullOldPath = getPath(rootPath, oldKey);
|
||||
const oldValue = get(config, fullOldPath);
|
||||
if (oldValue === undefined) {
|
||||
return config;
|
||||
}
|
||||
|
||||
unset(config, fullOldPath);
|
||||
|
||||
const fullNewPath = getPath(rootPath, newKey);
|
||||
const newValue = get(config, fullNewPath);
|
||||
if (newValue === undefined) {
|
||||
set(config, fullNewPath, oldValue);
|
||||
log(`"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`);
|
||||
} else {
|
||||
log(
|
||||
`"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"`
|
||||
);
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
||||
const _unused = (
|
||||
config: Record<string, any>,
|
||||
rootPath: string,
|
||||
log: ConfigDeprecationLogger,
|
||||
unusedKey: string
|
||||
) => {
|
||||
const fullPath = getPath(rootPath, unusedKey);
|
||||
if (get(config, fullPath) === undefined) {
|
||||
return config;
|
||||
}
|
||||
unset(config, fullPath);
|
||||
log(`${fullPath} is deprecated and is no longer used`);
|
||||
return config;
|
||||
};
|
||||
|
||||
const rename = (oldKey: string, newKey: string): ConfigDeprecation => (config, rootPath, log) =>
|
||||
_rename(config, rootPath, log, oldKey, newKey);
|
||||
|
||||
const renameFromRoot = (oldKey: string, newKey: string): ConfigDeprecation => (
|
||||
config,
|
||||
rootPath,
|
||||
log
|
||||
) => _rename(config, '', log, oldKey, newKey);
|
||||
|
||||
const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) =>
|
||||
_unused(config, rootPath, log, unusedKey);
|
||||
|
||||
const unusedFromRoot = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) =>
|
||||
_unused(config, '', log, unusedKey);
|
||||
|
||||
const getPath = (rootPath: string, subPath: string) =>
|
||||
rootPath !== '' ? `${rootPath}.${subPath}` : subPath;
|
||||
|
||||
/**
|
||||
* The actual platform implementation of {@link ConfigDeprecationFactory}
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const configDeprecationFactory: ConfigDeprecationFactory = {
|
||||
rename,
|
||||
renameFromRoot,
|
||||
unused,
|
||||
unusedFromRoot,
|
||||
};
|
|
@ -17,19 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { createRoot } from '../../../../../test_utils/kbn_server';
|
||||
|
||||
(async function run() {
|
||||
const root = createRoot(JSON.parse(process.env.CREATE_SERVER_OPTS));
|
||||
|
||||
// We just need the server to run through startup so that it will
|
||||
// log the deprecation messages. Once it has started up we close it
|
||||
// to allow the process to exit naturally
|
||||
try {
|
||||
await root.setup();
|
||||
await root.start();
|
||||
} finally {
|
||||
await root.shutdown();
|
||||
}
|
||||
|
||||
}());
|
||||
export {
|
||||
ConfigDeprecation,
|
||||
ConfigDeprecationWithContext,
|
||||
ConfigDeprecationLogger,
|
||||
ConfigDeprecationFactory,
|
||||
ConfigDeprecationProvider,
|
||||
} from './types';
|
||||
export { configDeprecationFactory } from './deprecation_factory';
|
||||
export { coreDeprecationProvider } from './core_deprecations';
|
||||
export { applyDeprecations } from './apply_deprecations';
|
141
src/core/server/config/deprecation/types.ts
Normal file
141
src/core/server/config/deprecation/types.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Logger interface used when invoking a {@link ConfigDeprecation}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ConfigDeprecationLogger = (message: string) => void;
|
||||
|
||||
/**
|
||||
* Configuration deprecation returned from {@link ConfigDeprecationProvider} that handles a single deprecation from the configuration.
|
||||
*
|
||||
* @remarks
|
||||
* This should only be manually implemented if {@link ConfigDeprecationFactory} does not provide the proper helpers for a specific
|
||||
* deprecation need.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ConfigDeprecation = (
|
||||
config: Record<string, any>,
|
||||
fromPath: string,
|
||||
logger: ConfigDeprecationLogger
|
||||
) => Record<string, any>;
|
||||
|
||||
/**
|
||||
* A provider that should returns a list of {@link ConfigDeprecation}.
|
||||
*
|
||||
* See {@link ConfigDeprecationFactory} for more usage examples.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const provider: ConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
* rename('oldKey', 'newKey'),
|
||||
* unused('deprecatedKey'),
|
||||
* myCustomDeprecation,
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => ConfigDeprecation[];
|
||||
|
||||
/**
|
||||
* Provides helpers to generates the most commonly used {@link ConfigDeprecation}
|
||||
* when invoking a {@link ConfigDeprecationProvider}.
|
||||
*
|
||||
* See methods documentation for more detailed examples.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const provider: ConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
* rename('oldKey', 'newKey'),
|
||||
* unused('deprecatedKey'),
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ConfigDeprecationFactory {
|
||||
/**
|
||||
* Rename a configuration property from inside a plugin's configuration path.
|
||||
* Will log a deprecation warning if the oldKey was found and deprecation applied.
|
||||
*
|
||||
* @example
|
||||
* Rename 'myplugin.oldKey' to 'myplugin.newKey'
|
||||
* ```typescript
|
||||
* const provider: ConfigDeprecationProvider = ({ rename }) => [
|
||||
* rename('oldKey', 'newKey'),
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
rename(oldKey: string, newKey: string): ConfigDeprecation;
|
||||
/**
|
||||
* Rename a configuration property from the root configuration.
|
||||
* Will log a deprecation warning if the oldKey was found and deprecation applied.
|
||||
*
|
||||
* This should be only used when renaming properties from different configuration's path.
|
||||
* To rename properties from inside a plugin's configuration, use 'rename' instead.
|
||||
*
|
||||
* @example
|
||||
* Rename 'oldplugin.key' to 'newplugin.key'
|
||||
* ```typescript
|
||||
* const provider: ConfigDeprecationProvider = ({ renameFromRoot }) => [
|
||||
* renameFromRoot('oldplugin.key', 'newplugin.key'),
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation;
|
||||
/**
|
||||
* Remove a configuration property from inside a plugin's configuration path.
|
||||
* Will log a deprecation warning if the unused key was found and deprecation applied.
|
||||
*
|
||||
* @example
|
||||
* Flags 'myplugin.deprecatedKey' as unused
|
||||
* ```typescript
|
||||
* const provider: ConfigDeprecationProvider = ({ unused }) => [
|
||||
* unused('deprecatedKey'),
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
unused(unusedKey: string): ConfigDeprecation;
|
||||
/**
|
||||
* Remove a configuration property from the root configuration.
|
||||
* Will log a deprecation warning if the unused key was found and deprecation applied.
|
||||
*
|
||||
* This should be only used when removing properties from outside of a plugin's configuration.
|
||||
* To remove properties from inside a plugin's configuration, use 'unused' instead.
|
||||
*
|
||||
* @example
|
||||
* Flags 'somepath.deprecatedProperty' as unused
|
||||
* ```typescript
|
||||
* const provider: ConfigDeprecationProvider = ({ unusedFromRoot }) => [
|
||||
* unusedFromRoot('somepath.deprecatedProperty'),
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
unusedFromRoot(unusedKey: string): ConfigDeprecation;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface ConfigDeprecationWithContext {
|
||||
deprecation: ConfigDeprecation;
|
||||
path: string;
|
||||
}
|
|
@ -18,9 +18,16 @@
|
|||
*/
|
||||
|
||||
export { ConfigService, IConfigService } from './config_service';
|
||||
export { RawConfigService } from './raw_config_service';
|
||||
export { RawConfigService, RawConfigurationProvider } from './raw_config_service';
|
||||
export { Config, ConfigPath, isConfigPath, hasConfigPathIntersection } from './config';
|
||||
export { ObjectToConfigAdapter } from './object_to_config_adapter';
|
||||
export { CliArgs, Env } from './env';
|
||||
export {
|
||||
ConfigDeprecation,
|
||||
ConfigDeprecationLogger,
|
||||
ConfigDeprecationProvider,
|
||||
ConfigDeprecationFactory,
|
||||
coreDeprecationProvider,
|
||||
} from './deprecation';
|
||||
|
||||
export { EnvironmentMode, PackageInfo } from './types';
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { transformDeprecations } from './transform_deprecations';
|
||||
|
||||
export function configDeprecationWarningsMixin(kbnServer, server) {
|
||||
transformDeprecations(kbnServer.settings, (message) => {
|
||||
server.log(['warning', 'config', 'deprecation'], message);
|
||||
});
|
||||
}
|
||||
import { loggingServiceMock } from '../../logging/logging_service.mock';
|
||||
export const mockLoggingService = loggingServiceMock.create();
|
||||
mockLoggingService.asLoggerFactory.mockImplementation(() => mockLoggingService);
|
||||
jest.doMock('../../logging/logging_service', () => ({
|
||||
LoggingService: jest.fn(() => mockLoggingService),
|
||||
}));
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { mockLoggingService } from './config_deprecation.test.mocks';
|
||||
import { loggingServiceMock } from '../../logging/logging_service.mock';
|
||||
import * as kbnTestServer from '../../../../test_utils/kbn_server';
|
||||
|
||||
describe('configuration deprecations', () => {
|
||||
let root: ReturnType<typeof kbnTestServer.createRoot>;
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await root.shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
it('should not log deprecation warnings for default configuration', async () => {
|
||||
root = kbnTestServer.createRoot();
|
||||
|
||||
await root.setup();
|
||||
|
||||
const logs = loggingServiceMock.collect(mockLoggingService);
|
||||
expect(logs.warn).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
|
||||
it('should log deprecation warnings for core deprecations', async () => {
|
||||
root = kbnTestServer.createRoot({
|
||||
optimize: {
|
||||
lazy: true,
|
||||
lazyPort: 9090,
|
||||
},
|
||||
});
|
||||
|
||||
await root.setup();
|
||||
|
||||
const logs = loggingServiceMock.collect(mockLoggingService);
|
||||
expect(logs.warn).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"\\"optimize.lazy\\" is deprecated and has been replaced by \\"optimize.watch\\"",
|
||||
],
|
||||
Array [
|
||||
"\\"optimize.lazyPort\\" is deprecated and has been replaced by \\"optimize.watchPort\\"",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
39
src/core/server/config/raw_config_service.mock.ts
Normal file
39
src/core/server/config/raw_config_service.mock.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { RawConfigService } from './raw_config_service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
const createRawConfigServiceMock = ({
|
||||
rawConfig = {},
|
||||
rawConfig$ = undefined,
|
||||
}: { rawConfig?: Record<string, any>; rawConfig$?: Observable<Record<string, any>> } = {}) => {
|
||||
const mocked: jest.Mocked<PublicMethodsOf<RawConfigService>> = {
|
||||
loadConfig: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
reloadConfig: jest.fn(),
|
||||
getConfig$: jest.fn().mockReturnValue(rawConfig$ || of(rawConfig)),
|
||||
};
|
||||
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const rawConfigServiceMock = {
|
||||
create: createRawConfigServiceMock,
|
||||
};
|
|
@ -88,8 +88,8 @@ test('returns config at path as observable', async () => {
|
|||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
expect(exampleConfig.get('key')).toEqual('value');
|
||||
expect(exampleConfig.getFlattenedPaths()).toEqual(['key']);
|
||||
expect(exampleConfig.key).toEqual('value');
|
||||
expect(Object.keys(exampleConfig)).toEqual(['key']);
|
||||
});
|
||||
|
||||
test("pushes new configs when reloading even if config at path hasn't changed", async () => {
|
||||
|
@ -110,19 +110,15 @@ test("pushes new configs when reloading even if config at path hasn't changed",
|
|||
configService.reloadConfig();
|
||||
|
||||
expect(valuesReceived).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
ObjectToConfigAdapter {
|
||||
"rawConfig": Object {
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
ObjectToConfigAdapter {
|
||||
"rawConfig": Object {
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Object {
|
||||
"key": "value",
|
||||
},
|
||||
Object {
|
||||
"key": "value",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('pushes new config when reloading and config at path has changed', async () => {
|
||||
|
@ -143,10 +139,10 @@ test('pushes new config when reloading and config at path has changed', async ()
|
|||
configService.reloadConfig();
|
||||
|
||||
expect(valuesReceived).toHaveLength(2);
|
||||
expect(valuesReceived[0].get('key')).toEqual('value');
|
||||
expect(valuesReceived[0].getFlattenedPaths()).toEqual(['key']);
|
||||
expect(valuesReceived[1].get('key')).toEqual('new value');
|
||||
expect(valuesReceived[1].getFlattenedPaths()).toEqual(['key']);
|
||||
expect(valuesReceived[0].key).toEqual('value');
|
||||
expect(Object.keys(valuesReceived[0])).toEqual(['key']);
|
||||
expect(valuesReceived[1].key).toEqual('new value');
|
||||
expect(Object.keys(valuesReceived[1])).toEqual(['key']);
|
||||
});
|
||||
|
||||
test('completes config observables when stopped', done => {
|
||||
|
|
|
@ -22,10 +22,12 @@ import { Observable, ReplaySubject } from 'rxjs';
|
|||
import { map } from 'rxjs/operators';
|
||||
import typeDetect from 'type-detect';
|
||||
|
||||
import { Config } from './config';
|
||||
import { ObjectToConfigAdapter } from './object_to_config_adapter';
|
||||
import { getConfigFromFiles } from './read_config';
|
||||
|
||||
type RawConfigAdapter = (rawConfig: Record<string, any>) => Record<string, any>;
|
||||
|
||||
export type RawConfigurationProvider = Pick<RawConfigService, 'getConfig$'>;
|
||||
|
||||
/** @internal */
|
||||
export class RawConfigService {
|
||||
/**
|
||||
|
@ -35,12 +37,11 @@ export class RawConfigService {
|
|||
*/
|
||||
private readonly rawConfigFromFile$: ReplaySubject<Record<string, any>> = new ReplaySubject(1);
|
||||
|
||||
private readonly config$: Observable<Config>;
|
||||
private readonly config$: Observable<Record<string, any>>;
|
||||
|
||||
constructor(
|
||||
public readonly configFiles: readonly string[],
|
||||
configAdapter: (rawConfig: Record<string, any>) => Config = rawConfig =>
|
||||
new ObjectToConfigAdapter(rawConfig)
|
||||
configAdapter: RawConfigAdapter = rawConfig => rawConfig
|
||||
) {
|
||||
this.config$ = this.rawConfigFromFile$.pipe(
|
||||
map(rawConfig => {
|
||||
|
|
|
@ -24,7 +24,7 @@ import { BehaviorSubject } from 'rxjs';
|
|||
import { HttpService } from '.';
|
||||
import { HttpConfigType, config } from './http_config';
|
||||
import { httpServerMock } from './http_server.mocks';
|
||||
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
|
||||
import { ConfigService, Env } from '../config';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { contextServiceMock } from '../context/context_service.mock';
|
||||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
|
@ -35,11 +35,12 @@ const coreId = Symbol();
|
|||
|
||||
const createConfigService = (value: Partial<HttpConfigType> = {}) => {
|
||||
const configService = new ConfigService(
|
||||
new BehaviorSubject<Config>(
|
||||
new ObjectToConfigAdapter({
|
||||
server: value,
|
||||
})
|
||||
),
|
||||
{
|
||||
getConfig$: () =>
|
||||
new BehaviorSubject({
|
||||
server: value,
|
||||
}),
|
||||
},
|
||||
env,
|
||||
logger
|
||||
);
|
||||
|
|
|
@ -50,7 +50,16 @@ import { CapabilitiesSetup, CapabilitiesStart } from './capabilities';
|
|||
|
||||
export { bootstrap } from './bootstrap';
|
||||
export { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './capabilities';
|
||||
export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config';
|
||||
export {
|
||||
ConfigPath,
|
||||
ConfigService,
|
||||
ConfigDeprecation,
|
||||
ConfigDeprecationProvider,
|
||||
ConfigDeprecationLogger,
|
||||
ConfigDeprecationFactory,
|
||||
EnvironmentMode,
|
||||
PackageInfo,
|
||||
} from './config';
|
||||
export {
|
||||
IContextContainer,
|
||||
IContextProvider,
|
||||
|
|
|
@ -50,7 +50,7 @@ describe('ensureValidConfiguration', () => {
|
|||
coreHandledConfigPaths: ['core', 'elastic'],
|
||||
pluginSpecs: 'pluginSpecs',
|
||||
disabledPluginSpecs: 'disabledPluginSpecs',
|
||||
inputSettings: 'settings',
|
||||
settings: 'settings',
|
||||
legacyConfig: 'pluginExtendedConfig',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ export async function ensureValidConfiguration(
|
|||
coreHandledConfigPaths: await configService.getUsedPaths(),
|
||||
pluginSpecs,
|
||||
disabledPluginSpecs,
|
||||
inputSettings: settings,
|
||||
settings,
|
||||
legacyConfig: pluginExtendedConfig,
|
||||
});
|
||||
|
||||
|
|
|
@ -20,17 +20,10 @@
|
|||
import { LegacyPluginSpec } from '../plugins/find_legacy_plugin_specs';
|
||||
import { LegacyConfig } from './types';
|
||||
import { getUnusedConfigKeys } from './get_unused_config_keys';
|
||||
// @ts-ignore
|
||||
import { transformDeprecations } from '../../../../legacy/server/config/transform_deprecations';
|
||||
|
||||
jest.mock('../../../../legacy/server/config/transform_deprecations', () => ({
|
||||
transformDeprecations: jest.fn().mockImplementation(s => s),
|
||||
}));
|
||||
|
||||
describe('getUnusedConfigKeys', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
transformDeprecations.mockImplementation((s: any) => s);
|
||||
});
|
||||
|
||||
const getConfig = (values: Record<string, any> = {}): LegacyConfig =>
|
||||
|
@ -45,7 +38,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {},
|
||||
settings: {},
|
||||
legacyConfig: getConfig(),
|
||||
})
|
||||
).toEqual([]);
|
||||
|
@ -57,7 +50,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
presentInBoth: true,
|
||||
alsoInBoth: 'someValue',
|
||||
},
|
||||
|
@ -75,7 +68,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
presentInBoth: true,
|
||||
},
|
||||
legacyConfig: getConfig({
|
||||
|
@ -92,7 +85,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
presentInBoth: true,
|
||||
onlyInSetting: 'value',
|
||||
},
|
||||
|
@ -109,7 +102,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
elasticsearch: {
|
||||
username: 'foo',
|
||||
password: 'bar',
|
||||
|
@ -131,7 +124,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
env: 'development',
|
||||
},
|
||||
legacyConfig: getConfig({
|
||||
|
@ -149,7 +142,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
prop: ['a', 'b', 'c'],
|
||||
},
|
||||
legacyConfig: getConfig({
|
||||
|
@ -171,7 +164,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
getConfigPrefix: () => 'foo.bar',
|
||||
} as unknown) as LegacyPluginSpec,
|
||||
],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
foo: {
|
||||
bar: {
|
||||
unused: true,
|
||||
|
@ -194,7 +187,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
coreHandledConfigPaths: ['core', 'foo.bar'],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
core: {
|
||||
prop: 'value',
|
||||
},
|
||||
|
@ -209,46 +202,6 @@ describe('getUnusedConfigKeys', () => {
|
|||
});
|
||||
|
||||
describe('using deprecation', () => {
|
||||
it('calls transformDeprecations with the settings', async () => {
|
||||
await getUnusedConfigKeys({
|
||||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
prop: 'settings',
|
||||
},
|
||||
legacyConfig: getConfig({
|
||||
prop: 'config',
|
||||
}),
|
||||
});
|
||||
expect(transformDeprecations).toHaveBeenCalledTimes(1);
|
||||
expect(transformDeprecations).toHaveBeenCalledWith({
|
||||
prop: 'settings',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the transformed settings', async () => {
|
||||
transformDeprecations.mockImplementation((settings: Record<string, any>) => {
|
||||
delete settings.deprecated;
|
||||
settings.updated = 'new value';
|
||||
return settings;
|
||||
});
|
||||
expect(
|
||||
await getUnusedConfigKeys({
|
||||
coreHandledConfigPaths: [],
|
||||
pluginSpecs: [],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
onlyInSettings: 'bar',
|
||||
deprecated: 'value',
|
||||
},
|
||||
legacyConfig: getConfig({
|
||||
updated: 'config',
|
||||
}),
|
||||
})
|
||||
).toEqual(['onlyInSettings']);
|
||||
});
|
||||
|
||||
it('should use the plugin deprecations provider', async () => {
|
||||
expect(
|
||||
await getUnusedConfigKeys({
|
||||
|
@ -262,7 +215,7 @@ describe('getUnusedConfigKeys', () => {
|
|||
} as unknown) as LegacyPluginSpec,
|
||||
],
|
||||
disabledPluginSpecs: [],
|
||||
inputSettings: {
|
||||
settings: {
|
||||
foo: {
|
||||
foo: 'dolly',
|
||||
foo1: 'bar',
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
import { difference, get, set } from 'lodash';
|
||||
// @ts-ignore
|
||||
import { transformDeprecations } from '../../../../legacy/server/config/transform_deprecations';
|
||||
// @ts-ignore
|
||||
import { getTransform } from '../../../../legacy/deprecation/index';
|
||||
import { unset, getFlattenedObject } from '../../../../legacy/utils';
|
||||
import { hasConfigPathIntersection } from '../../config';
|
||||
|
@ -33,18 +31,15 @@ export async function getUnusedConfigKeys({
|
|||
coreHandledConfigPaths,
|
||||
pluginSpecs,
|
||||
disabledPluginSpecs,
|
||||
inputSettings,
|
||||
settings,
|
||||
legacyConfig,
|
||||
}: {
|
||||
coreHandledConfigPaths: string[];
|
||||
pluginSpecs: LegacyPluginSpec[];
|
||||
disabledPluginSpecs: LegacyPluginSpec[];
|
||||
inputSettings: Record<string, any>;
|
||||
settings: Record<string, any>;
|
||||
legacyConfig: LegacyConfig;
|
||||
}) {
|
||||
// transform deprecated core settings
|
||||
const settings = transformDeprecations(inputSettings);
|
||||
|
||||
// transform deprecated plugin settings
|
||||
for (let i = 0; i < pluginSpecs.length; i++) {
|
||||
const spec = pluginSpecs[i];
|
||||
|
|
|
@ -19,4 +19,10 @@
|
|||
|
||||
export { ensureValidConfiguration } from './ensure_valid_configuration';
|
||||
export { LegacyObjectToConfigAdapter } from './legacy_object_to_config_adapter';
|
||||
export { LegacyConfig } from './types';
|
||||
export { convertLegacyDeprecationProvider } from './legacy_deprecation_adapters';
|
||||
export {
|
||||
LegacyConfig,
|
||||
LegacyConfigDeprecation,
|
||||
LegacyConfigDeprecationFactory,
|
||||
LegacyConfigDeprecationProvider,
|
||||
} from './types';
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { convertLegacyDeprecationProvider } from './legacy_deprecation_adapters';
|
||||
import { LegacyConfigDeprecationProvider } from './types';
|
||||
import { ConfigDeprecation } from '../../config';
|
||||
import { configDeprecationFactory } from '../../config/deprecation/deprecation_factory';
|
||||
import { applyDeprecations } from '../../config/deprecation/apply_deprecations';
|
||||
|
||||
jest.spyOn(configDeprecationFactory, 'unusedFromRoot');
|
||||
jest.spyOn(configDeprecationFactory, 'renameFromRoot');
|
||||
|
||||
const executeHandlers = (handlers: ConfigDeprecation[]) => {
|
||||
handlers.forEach(handler => {
|
||||
handler({}, '', () => null);
|
||||
});
|
||||
};
|
||||
|
||||
describe('convertLegacyDeprecationProvider', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns the same number of handlers', async () => {
|
||||
const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
rename('a', 'b'),
|
||||
unused('c'),
|
||||
unused('d'),
|
||||
];
|
||||
|
||||
const migrated = await convertLegacyDeprecationProvider(legacyProvider);
|
||||
const handlers = migrated(configDeprecationFactory);
|
||||
expect(handlers).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('invokes the factory "unusedFromRoot" when using legacy "unused"', async () => {
|
||||
const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
rename('a', 'b'),
|
||||
unused('c'),
|
||||
unused('d'),
|
||||
];
|
||||
|
||||
const migrated = await convertLegacyDeprecationProvider(legacyProvider);
|
||||
const handlers = migrated(configDeprecationFactory);
|
||||
executeHandlers(handlers);
|
||||
|
||||
expect(configDeprecationFactory.unusedFromRoot).toHaveBeenCalledTimes(2);
|
||||
expect(configDeprecationFactory.unusedFromRoot).toHaveBeenCalledWith('c');
|
||||
expect(configDeprecationFactory.unusedFromRoot).toHaveBeenCalledWith('d');
|
||||
});
|
||||
|
||||
it('invokes the factory "renameFromRoot" when using legacy "rename"', async () => {
|
||||
const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
rename('a', 'b'),
|
||||
unused('c'),
|
||||
rename('d', 'e'),
|
||||
];
|
||||
|
||||
const migrated = await convertLegacyDeprecationProvider(legacyProvider);
|
||||
const handlers = migrated(configDeprecationFactory);
|
||||
executeHandlers(handlers);
|
||||
|
||||
expect(configDeprecationFactory.renameFromRoot).toHaveBeenCalledTimes(2);
|
||||
expect(configDeprecationFactory.renameFromRoot).toHaveBeenCalledWith('a', 'b');
|
||||
expect(configDeprecationFactory.renameFromRoot).toHaveBeenCalledWith('d', 'e');
|
||||
});
|
||||
|
||||
it('properly works in a real use case', async () => {
|
||||
const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [
|
||||
rename('old', 'new'),
|
||||
unused('unused'),
|
||||
unused('notpresent'),
|
||||
];
|
||||
|
||||
const convertedProvider = await convertLegacyDeprecationProvider(legacyProvider);
|
||||
const handlers = convertedProvider(configDeprecationFactory);
|
||||
|
||||
const rawConfig = {
|
||||
old: 'oldvalue',
|
||||
unused: 'unused',
|
||||
goodValue: 'good',
|
||||
};
|
||||
|
||||
const migrated = applyDeprecations(
|
||||
rawConfig,
|
||||
handlers.map(handler => ({ deprecation: handler, path: '' }))
|
||||
);
|
||||
expect(migrated).toEqual({ new: 'oldvalue', goodValue: 'good' });
|
||||
});
|
||||
});
|
57
src/core/server/legacy/config/legacy_deprecation_adapters.ts
Normal file
57
src/core/server/legacy/config/legacy_deprecation_adapters.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { ConfigDeprecation, ConfigDeprecationProvider } from '../../config/deprecation';
|
||||
import { LegacyConfigDeprecation, LegacyConfigDeprecationProvider } from './index';
|
||||
import { configDeprecationFactory } from '../../config/deprecation/deprecation_factory';
|
||||
|
||||
const convertLegacyDeprecation = (
|
||||
legacyDeprecation: LegacyConfigDeprecation
|
||||
): ConfigDeprecation => (config, fromPath, logger) => {
|
||||
legacyDeprecation(config, logger);
|
||||
return config;
|
||||
};
|
||||
|
||||
const legacyUnused = (unusedKey: string): LegacyConfigDeprecation => (settings, log) => {
|
||||
const deprecation = configDeprecationFactory.unusedFromRoot(unusedKey);
|
||||
deprecation(settings, '', log);
|
||||
};
|
||||
|
||||
const legacyRename = (oldKey: string, newKey: string): LegacyConfigDeprecation => (
|
||||
settings,
|
||||
log
|
||||
) => {
|
||||
const deprecation = configDeprecationFactory.renameFromRoot(oldKey, newKey);
|
||||
deprecation(settings, '', log);
|
||||
};
|
||||
|
||||
/**
|
||||
* Async deprecation provider converter for legacy deprecation implementation
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const convertLegacyDeprecationProvider = async (
|
||||
legacyProvider: LegacyConfigDeprecationProvider
|
||||
): Promise<ConfigDeprecationProvider> => {
|
||||
const legacyDeprecations = await legacyProvider({
|
||||
rename: legacyRename,
|
||||
unused: legacyUnused,
|
||||
});
|
||||
return () => legacyDeprecations.map(convertLegacyDeprecation);
|
||||
};
|
|
@ -17,7 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ConfigPath, ObjectToConfigAdapter } from '../../config';
|
||||
import { ConfigPath } from '../../config';
|
||||
import { ObjectToConfigAdapter } from '../../config/object_to_config_adapter';
|
||||
|
||||
/**
|
||||
* Represents logging config supported by the legacy platform.
|
||||
|
|
|
@ -26,3 +26,33 @@ export interface LegacyConfig {
|
|||
get<T>(key?: string): T;
|
||||
has(key: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of a legacy configuration deprecation factory used for
|
||||
* legacy plugin deprecations.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface LegacyConfigDeprecationFactory {
|
||||
rename(oldKey: string, newKey: string): LegacyConfigDeprecation;
|
||||
unused(unusedKey: string): LegacyConfigDeprecation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of a legacy configuration deprecation.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type LegacyConfigDeprecation = (
|
||||
settings: Record<string, any>,
|
||||
log: (msg: string) => void
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Representation of a legacy configuration deprecation provider.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type LegacyConfigDeprecationProvider = (
|
||||
factory: LegacyConfigDeprecationFactory
|
||||
) => LegacyConfigDeprecation[] | Promise<LegacyConfigDeprecation[]>;
|
||||
|
|
|
@ -21,13 +21,9 @@ import { BehaviorSubject, throwError } from 'rxjs';
|
|||
|
||||
jest.mock('../../../legacy/server/kbn_server');
|
||||
jest.mock('../../../cli/cluster/cluster_manager');
|
||||
jest.mock('./plugins/find_legacy_plugin_specs.ts', () => ({
|
||||
findLegacyPluginSpecs: (settings: Record<string, any>) => ({
|
||||
pluginSpecs: [],
|
||||
pluginExtendedConfig: settings,
|
||||
disabledPluginSpecs: [],
|
||||
uiExports: [],
|
||||
}),
|
||||
jest.mock('./plugins/find_legacy_plugin_specs');
|
||||
jest.mock('./config/legacy_deprecation_adapters', () => ({
|
||||
convertLegacyDeprecationProvider: (provider: any) => Promise.resolve(provider),
|
||||
}));
|
||||
|
||||
import { LegacyService, LegacyServiceSetupDeps, LegacyServiceStartDeps } from '.';
|
||||
|
@ -47,8 +43,10 @@ import { httpServiceMock } from '../http/http_service.mock';
|
|||
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
|
||||
import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock';
|
||||
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
|
||||
import { findLegacyPluginSpecs } from './plugins/find_legacy_plugin_specs';
|
||||
|
||||
const MockKbnServer: jest.Mock<KbnServer> = KbnServer as any;
|
||||
const findLegacyPluginSpecsMock: jest.Mock<typeof findLegacyPluginSpecs> = findLegacyPluginSpecs as any;
|
||||
|
||||
let coreId: symbol;
|
||||
let env: Env;
|
||||
|
@ -66,6 +64,16 @@ beforeEach(() => {
|
|||
env = Env.createDefault(getEnvOptions());
|
||||
configService = configServiceMock.create();
|
||||
|
||||
findLegacyPluginSpecsMock.mockImplementation(
|
||||
settings =>
|
||||
Promise.resolve({
|
||||
pluginSpecs: [],
|
||||
pluginExtendedConfig: settings,
|
||||
disabledPluginSpecs: [],
|
||||
uiExports: [],
|
||||
}) as any
|
||||
);
|
||||
|
||||
MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve());
|
||||
|
||||
setupDeps = {
|
||||
|
@ -115,6 +123,7 @@ beforeEach(() => {
|
|||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
findLegacyPluginSpecsMock.mockReset();
|
||||
});
|
||||
|
||||
describe('once LegacyService is set up with connection info', () => {
|
||||
|
@ -382,3 +391,52 @@ test('Cannot start without setup phase', async () => {
|
|||
`"Legacy service is not setup yet."`
|
||||
);
|
||||
});
|
||||
|
||||
describe('#discoverPlugins()', () => {
|
||||
it('calls findLegacyPluginSpecs with correct parameters', async () => {
|
||||
const legacyService = new LegacyService({
|
||||
coreId,
|
||||
env,
|
||||
logger,
|
||||
configService: configService as any,
|
||||
});
|
||||
|
||||
await legacyService.discoverPlugins();
|
||||
expect(findLegacyPluginSpecs).toHaveBeenCalledTimes(1);
|
||||
expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger);
|
||||
});
|
||||
|
||||
it(`register legacy plugin's deprecation providers`, async () => {
|
||||
findLegacyPluginSpecsMock.mockImplementation(
|
||||
settings =>
|
||||
Promise.resolve({
|
||||
pluginSpecs: [
|
||||
{
|
||||
getDeprecationsProvider: () => undefined,
|
||||
},
|
||||
{
|
||||
getDeprecationsProvider: () => 'providerA',
|
||||
},
|
||||
{
|
||||
getDeprecationsProvider: () => 'providerB',
|
||||
},
|
||||
],
|
||||
pluginExtendedConfig: settings,
|
||||
disabledPluginSpecs: [],
|
||||
uiExports: [],
|
||||
}) as any
|
||||
);
|
||||
|
||||
const legacyService = new LegacyService({
|
||||
coreId,
|
||||
env,
|
||||
logger,
|
||||
configService: configService as any,
|
||||
});
|
||||
|
||||
await legacyService.discoverPlugins();
|
||||
expect(configService.addDeprecationProvider).toHaveBeenCalledTimes(2);
|
||||
expect(configService.addDeprecationProvider).toHaveBeenCalledWith('', 'providerA');
|
||||
expect(configService.addDeprecationProvider).toHaveBeenCalledWith('', 'providerB');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ import { CoreService } from '../../types';
|
|||
import { CoreSetup, CoreStart } from '../';
|
||||
import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
|
||||
import { SavedObjectsLegacyUiExports } from '../types';
|
||||
import { Config } from '../config';
|
||||
import { Config, ConfigDeprecationProvider } from '../config';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { DevConfig, DevConfigType } from '../dev';
|
||||
import { BasePathProxyServer, HttpConfig, HttpConfigType } from '../http';
|
||||
|
@ -32,7 +32,7 @@ import { PluginsServiceSetup, PluginsServiceStart } from '../plugins';
|
|||
import { findLegacyPluginSpecs } from './plugins';
|
||||
import { LegacyPluginSpec } from './plugins/find_legacy_plugin_specs';
|
||||
import { PathConfigType } from '../path';
|
||||
import { LegacyConfig } from './config';
|
||||
import { LegacyConfig, convertLegacyDeprecationProvider } from './config';
|
||||
|
||||
interface LegacyKbnServer {
|
||||
applyLoggingConfiguration: (settings: Readonly<Record<string, any>>) => void;
|
||||
|
@ -157,6 +157,18 @@ export class LegacyService implements CoreService {
|
|||
uiExports,
|
||||
};
|
||||
|
||||
const deprecationProviders = await pluginSpecs
|
||||
.map(spec => spec.getDeprecationsProvider())
|
||||
.reduce(async (providers, current) => {
|
||||
if (current) {
|
||||
return [...(await providers), await convertLegacyDeprecationProvider(current)];
|
||||
}
|
||||
return providers;
|
||||
}, Promise.resolve([] as ConfigDeprecationProvider[]));
|
||||
deprecationProviders.forEach(provider =>
|
||||
this.coreContext.configService.addDeprecationProvider('', provider)
|
||||
);
|
||||
|
||||
this.legacyRawConfig = pluginExtendedConfig;
|
||||
|
||||
// check for unknown uiExport types
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import { ServerExtType } from 'hapi';
|
||||
import Podium from 'podium';
|
||||
// @ts-ignore: implicit any for JS file
|
||||
import { Config, transformDeprecations } from '../../../../legacy/server/config';
|
||||
import { Config } from '../../../../legacy/server/config';
|
||||
// @ts-ignore: implicit any for JS file
|
||||
import { setupLogging } from '../../../../legacy/server/logging';
|
||||
import { LogLevel } from '../../logging/log_level';
|
||||
|
@ -99,7 +99,7 @@ export class LegacyLoggingServer {
|
|||
ops: { interval: 2147483647 },
|
||||
};
|
||||
|
||||
setupLogging(this, Config.withDefaultSchema(transformDeprecations(config)));
|
||||
setupLogging(this, Config.withDefaultSchema(config));
|
||||
}
|
||||
|
||||
public register({ plugin: { register }, options }: PluginRegisterParams): Promise<void> {
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
import { LoggerFactory } from '../../logging';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { collectUiExports as collectLegacyUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports';
|
||||
import { LegacyConfig } from '../config';
|
||||
import { LegacyConfig, LegacyConfigDeprecationProvider } from '../config';
|
||||
|
||||
export interface LegacyPluginPack {
|
||||
getPath(): string;
|
||||
|
@ -37,6 +37,7 @@ export interface LegacyPluginSpec {
|
|||
getId: () => unknown;
|
||||
getExpectedKibanaVersion: () => string;
|
||||
getConfigPrefix: () => string;
|
||||
getDeprecationsProvider: () => LegacyConfigDeprecationProvider | undefined;
|
||||
}
|
||||
|
||||
export async function findLegacyPluginSpecs(settings: unknown, loggerFactory: LoggerFactory) {
|
||||
|
|
|
@ -45,7 +45,7 @@ const createLoggingServiceMock = () => {
|
|||
context,
|
||||
...mockLog,
|
||||
}));
|
||||
mocked.asLoggerFactory.mockImplementation(() => createLoggingServiceMock());
|
||||
mocked.asLoggerFactory.mockImplementation(() => mocked);
|
||||
mocked.stop.mockResolvedValue();
|
||||
return mocked;
|
||||
};
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
import { mockPackage, mockReaddir, mockReadFile, mockStat } from './plugins_discovery.test.mocks';
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { first, map, toArray } from 'rxjs/operators';
|
||||
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../../config';
|
||||
import { ConfigService, Env } from '../../config';
|
||||
import { rawConfigServiceMock } from '../../config/raw_config_service.mock';
|
||||
import { getEnvOptions } from '../../config/__mocks__/env';
|
||||
import { loggingServiceMock } from '../../logging/logging_service.mock';
|
||||
import { PluginWrapper } from '../plugin';
|
||||
|
@ -115,9 +115,7 @@ test('properly iterates through plugin search locations', async () => {
|
|||
})
|
||||
);
|
||||
const configService = new ConfigService(
|
||||
new BehaviorSubject<Config>(
|
||||
new ObjectToConfigAdapter({ plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } })
|
||||
),
|
||||
rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }),
|
||||
env,
|
||||
logger
|
||||
);
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
*/
|
||||
|
||||
import { duration } from 'moment';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { createPluginInitializerContext } from './plugin_context';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { Env, ObjectToConfigAdapter } from '../config';
|
||||
import { Env } from '../config';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { rawConfigServiceMock } from '../config/raw_config_service.mock';
|
||||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { PluginManifest } from './types';
|
||||
import { Server } from '../server';
|
||||
|
@ -54,9 +54,9 @@ describe('Plugin Context', () => {
|
|||
beforeEach(async () => {
|
||||
coreId = Symbol('core');
|
||||
env = Env.createDefault(getEnvOptions());
|
||||
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({}));
|
||||
const config$ = rawConfigServiceMock.create({ rawConfig: {} });
|
||||
server = new Server(config$, env, logger);
|
||||
await server.setupConfigSchemas();
|
||||
await server.setupCoreConfig();
|
||||
coreContext = { coreId, env, logger, configService: server.configService };
|
||||
});
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ import { resolve, join } from 'path';
|
|||
import { BehaviorSubject, from } from 'rxjs';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { Config, ConfigPath, ConfigService, Env, ObjectToConfigAdapter } from '../config';
|
||||
import { ConfigPath, ConfigService, Env } from '../config';
|
||||
import { rawConfigServiceMock } from '../config/raw_config_service.mock';
|
||||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { coreMock } from '../mocks';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
|
@ -38,7 +39,7 @@ import { DiscoveredPlugin } from './types';
|
|||
const MockPluginsSystem: jest.Mock<PluginsSystem> = PluginsSystem as any;
|
||||
|
||||
let pluginsService: PluginsService;
|
||||
let config$: BehaviorSubject<Config>;
|
||||
let config$: BehaviorSubject<Record<string, any>>;
|
||||
let configService: ConfigService;
|
||||
let coreId: symbol;
|
||||
let env: Env;
|
||||
|
@ -109,10 +110,9 @@ describe('PluginsService', () => {
|
|||
coreId = Symbol('core');
|
||||
env = Env.createDefault(getEnvOptions());
|
||||
|
||||
config$ = new BehaviorSubject<Config>(
|
||||
new ObjectToConfigAdapter({ plugins: { initialize: true } })
|
||||
);
|
||||
configService = new ConfigService(config$, env, logger);
|
||||
config$ = new BehaviorSubject<Record<string, any>>({ plugins: { initialize: true } });
|
||||
const rawConfigService = rawConfigServiceMock.create({ rawConfig$: config$ });
|
||||
configService = new ConfigService(rawConfigService, env, logger);
|
||||
await configService.setSchema(config.path, config.schema);
|
||||
pluginsService = new PluginsService({ coreId, env, logger, configService });
|
||||
|
||||
|
@ -388,6 +388,40 @@ describe('PluginsService', () => {
|
|||
await pluginsService.discover();
|
||||
expect(configService.setSchema).toBeCalledWith('path', configSchema);
|
||||
});
|
||||
|
||||
it('registers plugin config deprecation provider in config service', async () => {
|
||||
const configSchema = schema.string();
|
||||
jest.spyOn(configService, 'setSchema').mockImplementation(() => Promise.resolve());
|
||||
jest.spyOn(configService, 'addDeprecationProvider');
|
||||
|
||||
const deprecationProvider = () => [];
|
||||
jest.doMock(
|
||||
join('path-with-provider', 'server'),
|
||||
() => ({
|
||||
config: {
|
||||
schema: configSchema,
|
||||
deprecations: deprecationProvider,
|
||||
},
|
||||
}),
|
||||
{
|
||||
virtual: true,
|
||||
}
|
||||
);
|
||||
mockDiscover.mockReturnValue({
|
||||
error$: from([]),
|
||||
plugin$: from([
|
||||
createPlugin('some-id', {
|
||||
path: 'path-with-provider',
|
||||
configPath: 'config-path',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
await pluginsService.discover();
|
||||
expect(configService.addDeprecationProvider).toBeCalledWith(
|
||||
'config-path',
|
||||
deprecationProvider
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#generateUiPluginsConfigs()', () => {
|
||||
|
@ -499,9 +533,7 @@ describe('PluginsService', () => {
|
|||
|
||||
mockPluginSystem.uiPlugins.mockReturnValue(new Map());
|
||||
|
||||
config$.next(
|
||||
new ObjectToConfigAdapter({ plugins: { initialize: true }, plugin1: { enabled: false } })
|
||||
);
|
||||
config$.next({ plugins: { initialize: true }, plugin1: { enabled: false } });
|
||||
|
||||
await pluginsService.discover();
|
||||
const { uiPlugins } = await pluginsService.setup({} as any);
|
||||
|
|
|
@ -196,6 +196,12 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
|
|||
const configDescriptor = plugin.getConfigDescriptor();
|
||||
if (configDescriptor) {
|
||||
this.pluginConfigDescriptors.set(plugin.name, configDescriptor);
|
||||
if (configDescriptor.deprecations) {
|
||||
this.coreContext.configService.addDeprecationProvider(
|
||||
plugin.configPath,
|
||||
configDescriptor.deprecations
|
||||
);
|
||||
}
|
||||
await this.coreContext.configService.setSchema(
|
||||
plugin.configPath,
|
||||
configDescriptor.schema
|
||||
|
|
|
@ -21,7 +21,7 @@ import { Observable } from 'rxjs';
|
|||
import { Type } from '@kbn/config-schema';
|
||||
|
||||
import { RecursiveReadonly } from 'kibana/public';
|
||||
import { ConfigPath, EnvironmentMode, PackageInfo } from '../config';
|
||||
import { ConfigPath, EnvironmentMode, PackageInfo, ConfigDeprecationProvider } from '../config';
|
||||
import { LoggerFactory } from '../logging';
|
||||
import { KibanaConfigType } from '../kibana_config';
|
||||
import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config';
|
||||
|
@ -36,7 +36,7 @@ import { CoreSetup, CoreStart } from '..';
|
|||
export type PluginConfigSchema<T> = Type<T>;
|
||||
|
||||
/**
|
||||
* Describes a plugin configuration schema and capabilities.
|
||||
* Describes a plugin configuration properties.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
|
@ -56,12 +56,20 @@ export type PluginConfigSchema<T> = Type<T>;
|
|||
* uiProp: true,
|
||||
* },
|
||||
* schema: configSchema,
|
||||
* deprecations: ({ rename, unused }) => [
|
||||
* rename('securityKey', 'secret'),
|
||||
* unused('deprecatedProperty'),
|
||||
* ],
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface PluginConfigDescriptor<T = any> {
|
||||
/**
|
||||
* Provider for the {@link ConfigDeprecation} to apply to the plugin configuration.
|
||||
*/
|
||||
deprecations?: ConfigDeprecationProvider;
|
||||
/**
|
||||
* List of configuration properties that will be available on the client-side plugin.
|
||||
*/
|
||||
|
|
|
@ -29,8 +29,14 @@ jest.doMock('../config/config_service', () => ({
|
|||
ConfigService: jest.fn(() => configService),
|
||||
}));
|
||||
|
||||
import { rawConfigServiceMock } from '../config/raw_config_service.mock';
|
||||
export const rawConfigService = rawConfigServiceMock.create();
|
||||
jest.doMock('../config/raw_config_service', () => ({
|
||||
RawConfigService: jest.fn(() => rawConfigService),
|
||||
}));
|
||||
|
||||
export const mockServer = {
|
||||
setupConfigSchemas: jest.fn(),
|
||||
setupCoreConfig: jest.fn(),
|
||||
setup: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
configService,
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { configService, logger, mockServer } from './index.test.mocks';
|
||||
import { rawConfigService, configService, logger, mockServer } from './index.test.mocks';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { filter, first } from 'rxjs/operators';
|
||||
|
@ -26,13 +26,13 @@ import { Env } from '../config';
|
|||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
|
||||
const env = new Env('.', getEnvOptions());
|
||||
const config$ = configService.getConfig$();
|
||||
|
||||
let mockConsoleError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(global.process, 'exit').mockReturnValue(undefined as never);
|
||||
mockConsoleError = jest.spyOn(console, 'error').mockReturnValue(undefined);
|
||||
rawConfigService.getConfig$.mockReturnValue(new BehaviorSubject({ someValue: 'foo' }));
|
||||
configService.atPath.mockReturnValue(new BehaviorSubject({ someValue: 'foo' }));
|
||||
});
|
||||
|
||||
|
@ -40,7 +40,7 @@ afterEach(() => {
|
|||
jest.restoreAllMocks();
|
||||
logger.asLoggerFactory.mockClear();
|
||||
logger.stop.mockClear();
|
||||
configService.getConfig$.mockClear();
|
||||
rawConfigService.getConfig$.mockClear();
|
||||
|
||||
logger.upgrade.mockReset();
|
||||
configService.atPath.mockReset();
|
||||
|
@ -49,7 +49,7 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
test('sets up services on "setup"', async () => {
|
||||
const root = new Root(config$, env);
|
||||
const root = new Root(rawConfigService, env);
|
||||
|
||||
expect(logger.upgrade).not.toHaveBeenCalled();
|
||||
expect(mockServer.setup).not.toHaveBeenCalled();
|
||||
|
@ -65,7 +65,7 @@ test('upgrades logging configuration after setup', async () => {
|
|||
const mockLoggingConfig$ = new BehaviorSubject({ someValue: 'foo' });
|
||||
configService.atPath.mockReturnValue(mockLoggingConfig$);
|
||||
|
||||
const root = new Root(config$, env);
|
||||
const root = new Root(rawConfigService, env);
|
||||
await root.setup();
|
||||
|
||||
expect(logger.upgrade).toHaveBeenCalledTimes(1);
|
||||
|
@ -80,7 +80,7 @@ test('upgrades logging configuration after setup', async () => {
|
|||
|
||||
test('stops services on "shutdown"', async () => {
|
||||
const mockOnShutdown = jest.fn();
|
||||
const root = new Root(config$, env, mockOnShutdown);
|
||||
const root = new Root(rawConfigService, env, mockOnShutdown);
|
||||
|
||||
await root.setup();
|
||||
|
||||
|
@ -98,7 +98,7 @@ test('stops services on "shutdown"', async () => {
|
|||
|
||||
test('stops services on "shutdown" an calls `onShutdown` with error passed to `shutdown`', async () => {
|
||||
const mockOnShutdown = jest.fn();
|
||||
const root = new Root(config$, env, mockOnShutdown);
|
||||
const root = new Root(rawConfigService, env, mockOnShutdown);
|
||||
|
||||
await root.setup();
|
||||
|
||||
|
@ -117,7 +117,7 @@ test('stops services on "shutdown" an calls `onShutdown` with error passed to `s
|
|||
|
||||
test('fails and stops services if server setup fails', async () => {
|
||||
const mockOnShutdown = jest.fn();
|
||||
const root = new Root(config$, env, mockOnShutdown);
|
||||
const root = new Root(rawConfigService, env, mockOnShutdown);
|
||||
|
||||
const serverError = new Error('server failed');
|
||||
mockServer.setup.mockRejectedValue(serverError);
|
||||
|
@ -136,7 +136,7 @@ test('fails and stops services if server setup fails', async () => {
|
|||
|
||||
test('fails and stops services if initial logger upgrade fails', async () => {
|
||||
const mockOnShutdown = jest.fn();
|
||||
const root = new Root(config$, env, mockOnShutdown);
|
||||
const root = new Root(rawConfigService, env, mockOnShutdown);
|
||||
|
||||
const loggingUpgradeError = new Error('logging config upgrade failed');
|
||||
logger.upgrade.mockImplementation(() => {
|
||||
|
@ -167,7 +167,7 @@ test('stops services if consequent logger upgrade fails', async () => {
|
|||
const mockLoggingConfig$ = new BehaviorSubject({ someValue: 'foo' });
|
||||
configService.atPath.mockReturnValue(mockLoggingConfig$);
|
||||
|
||||
const root = new Root(config$, env, mockOnShutdown);
|
||||
const root = new Root(rawConfigService, env, mockOnShutdown);
|
||||
await root.setup();
|
||||
|
||||
expect(mockOnShutdown).not.toHaveBeenCalled();
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ConnectableObservable, Observable, Subscription } from 'rxjs';
|
||||
import { ConnectableObservable, Subscription } from 'rxjs';
|
||||
import { first, map, publishReplay, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
import { Config, Env } from '../config';
|
||||
import { Env, RawConfigurationProvider } from '../config';
|
||||
import { Logger, LoggerFactory, LoggingConfigType, LoggingService } from '../logging';
|
||||
import { Server } from '../server';
|
||||
|
||||
|
@ -35,19 +35,19 @@ export class Root {
|
|||
private loggingConfigSubscription?: Subscription;
|
||||
|
||||
constructor(
|
||||
config$: Observable<Config>,
|
||||
rawConfigProvider: RawConfigurationProvider,
|
||||
env: Env,
|
||||
private readonly onShutdown?: (reason?: Error | string) => void
|
||||
) {
|
||||
this.loggingService = new LoggingService();
|
||||
this.logger = this.loggingService.asLoggerFactory();
|
||||
this.log = this.logger.get('root');
|
||||
this.server = new Server(config$, env, this.logger);
|
||||
this.server = new Server(rawConfigProvider, env, this.logger);
|
||||
}
|
||||
|
||||
public async setup() {
|
||||
try {
|
||||
await this.server.setupConfigSchemas();
|
||||
await this.server.setupCoreConfig();
|
||||
await this.setupLogging();
|
||||
this.log.debug('setting up root');
|
||||
return await this.server.setup();
|
||||
|
|
|
@ -502,15 +502,34 @@ export class ClusterClient implements IClusterClient {
|
|||
close(): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ConfigDeprecation = (config: Record<string, any>, fromPath: string, logger: ConfigDeprecationLogger) => Record<string, any>;
|
||||
|
||||
// @public
|
||||
export interface ConfigDeprecationFactory {
|
||||
rename(oldKey: string, newKey: string): ConfigDeprecation;
|
||||
renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation;
|
||||
unused(unusedKey: string): ConfigDeprecation;
|
||||
unusedFromRoot(unusedKey: string): ConfigDeprecation;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ConfigDeprecationLogger = (message: string) => void;
|
||||
|
||||
// @public
|
||||
export type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => ConfigDeprecation[];
|
||||
|
||||
// @public (undocumented)
|
||||
export type ConfigPath = string | string[];
|
||||
|
||||
// @internal (undocumented)
|
||||
export class ConfigService {
|
||||
// Warning: (ae-forgotten-export) The symbol "Config" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "RawConfigurationProvider" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "Env" needs to be exported by the entry point index.d.ts
|
||||
constructor(config$: Observable<Config>, env: Env, logger: LoggerFactory);
|
||||
constructor(rawConfigProvider: RawConfigurationProvider, env: Env, logger: LoggerFactory);
|
||||
addDeprecationProvider(path: ConfigPath, provider: ConfigDeprecationProvider): void;
|
||||
atPath<TSchema>(path: ConfigPath): Observable<TSchema>;
|
||||
// Warning: (ae-forgotten-export) The symbol "Config" needs to be exported by the entry point index.d.ts
|
||||
getConfig$(): Observable<Config>;
|
||||
// (undocumented)
|
||||
getUnusedPaths(): Promise<string[]>;
|
||||
|
@ -520,6 +539,7 @@ export class ConfigService {
|
|||
isEnabledAtPath(path: ConfigPath): Promise<boolean>;
|
||||
optionalAtPath<TSchema>(path: ConfigPath): Observable<TSchema | undefined>;
|
||||
setSchema(path: ConfigPath, schema: Type<unknown>): Promise<void>;
|
||||
validate(): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -1024,6 +1044,7 @@ export interface Plugin<TSetup = void, TStart = void, TPluginsSetup extends obje
|
|||
|
||||
// @public
|
||||
export interface PluginConfigDescriptor<T = any> {
|
||||
deprecations?: ConfigDeprecationProvider;
|
||||
exposeToBrowser?: {
|
||||
[P in keyof T]?: boolean;
|
||||
};
|
||||
|
@ -1792,9 +1813,9 @@ export const validBodyOutput: readonly ["data", "stream"];
|
|||
//
|
||||
// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:213:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:213:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:214:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:215:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:222:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts
|
||||
|
||||
```
|
||||
|
|
|
@ -29,14 +29,16 @@ import {
|
|||
} from './server.test.mocks';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Env, Config, ObjectToConfigAdapter } from './config';
|
||||
import { Env } from './config';
|
||||
import { Server } from './server';
|
||||
|
||||
import { getEnvOptions } from './config/__mocks__/env';
|
||||
import { loggingServiceMock } from './logging/logging_service.mock';
|
||||
import { rawConfigServiceMock } from './config/raw_config_service.mock';
|
||||
|
||||
const env = new Env('.', getEnvOptions());
|
||||
const logger = loggingServiceMock.create();
|
||||
const rawConfigService = rawConfigServiceMock.create({});
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
|
||||
|
@ -47,9 +49,8 @@ afterEach(() => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const config$ = new BehaviorSubject<Config>(new ObjectToConfigAdapter({}));
|
||||
test('sets up services on "setup"', async () => {
|
||||
const server = new Server(config$, env, logger);
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
|
||||
expect(mockHttpService.setup).not.toHaveBeenCalled();
|
||||
expect(mockElasticsearchService.setup).not.toHaveBeenCalled();
|
||||
|
@ -67,7 +68,7 @@ test('sets up services on "setup"', async () => {
|
|||
});
|
||||
|
||||
test('injects legacy dependency to context#setup()', async () => {
|
||||
const server = new Server(config$, env, logger);
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
|
||||
const pluginA = Symbol();
|
||||
const pluginB = Symbol();
|
||||
|
@ -89,7 +90,7 @@ test('injects legacy dependency to context#setup()', async () => {
|
|||
});
|
||||
|
||||
test('runs services on "start"', async () => {
|
||||
const server = new Server(config$, env, logger);
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
|
||||
expect(mockHttpService.setup).not.toHaveBeenCalled();
|
||||
expect(mockLegacyService.start).not.toHaveBeenCalled();
|
||||
|
@ -109,13 +110,13 @@ test('runs services on "start"', async () => {
|
|||
test('does not fail on "setup" if there are unused paths detected', async () => {
|
||||
mockConfigService.getUnusedPaths.mockResolvedValue(['some.path', 'another.path']);
|
||||
|
||||
const server = new Server(config$, env, logger);
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
|
||||
await expect(server.setup()).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
test('stops services on "stop"', async () => {
|
||||
const server = new Server(config$, env, logger);
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
|
||||
await server.setup();
|
||||
|
||||
|
@ -134,26 +135,25 @@ test('stops services on "stop"', async () => {
|
|||
expect(mockSavedObjectsService.stop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test(`doesn't setup core services if services config validation fails`, async () => {
|
||||
mockConfigService.setSchema.mockImplementation(() => {
|
||||
throw new Error('invalid config');
|
||||
test(`doesn't setup core services if config validation fails`, async () => {
|
||||
mockConfigService.validate.mockImplementationOnce(() => {
|
||||
return Promise.reject(new Error('invalid config'));
|
||||
});
|
||||
const server = new Server(config$, env, logger);
|
||||
await expect(server.setupConfigSchemas()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"invalid config"`
|
||||
);
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
await expect(server.setup()).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid config"`);
|
||||
|
||||
expect(mockHttpService.setup).not.toHaveBeenCalled();
|
||||
expect(mockElasticsearchService.setup).not.toHaveBeenCalled();
|
||||
expect(mockPluginsService.setup).not.toHaveBeenCalled();
|
||||
expect(mockLegacyService.setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test(`doesn't setup core services if config validation fails`, async () => {
|
||||
test(`doesn't setup core services if legacy config validation fails`, async () => {
|
||||
mockEnsureValidConfiguration.mockImplementation(() => {
|
||||
throw new Error('Unknown configuration keys');
|
||||
});
|
||||
|
||||
const server = new Server(config$, env, logger);
|
||||
const server = new Server(rawConfigService, env, logger);
|
||||
|
||||
await expect(server.setup()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Unknown configuration keys"`
|
||||
|
|
|
@ -16,11 +16,17 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { take } from 'rxjs/operators';
|
||||
import { Type } from '@kbn/config-schema';
|
||||
|
||||
import { ConfigService, Env, Config, ConfigPath } from './config';
|
||||
import {
|
||||
ConfigService,
|
||||
Env,
|
||||
ConfigPath,
|
||||
RawConfigurationProvider,
|
||||
coreDeprecationProvider,
|
||||
} from './config';
|
||||
import { ElasticsearchService } from './elasticsearch';
|
||||
import { HttpService, InternalHttpServiceSetup } from './http';
|
||||
import { LegacyService, ensureValidConfiguration } from './legacy';
|
||||
|
@ -44,6 +50,7 @@ import { InternalCoreSetup } from './internal_types';
|
|||
import { CapabilitiesService } from './capabilities';
|
||||
|
||||
const coreId = Symbol('core');
|
||||
const rootConfigPath = '';
|
||||
|
||||
export class Server {
|
||||
public readonly configService: ConfigService;
|
||||
|
@ -58,12 +65,12 @@ export class Server {
|
|||
private readonly uiSettings: UiSettingsService;
|
||||
|
||||
constructor(
|
||||
public readonly config$: Observable<Config>,
|
||||
rawConfigProvider: RawConfigurationProvider,
|
||||
public readonly env: Env,
|
||||
private readonly logger: LoggerFactory
|
||||
) {
|
||||
this.log = this.logger.get('server');
|
||||
this.configService = new ConfigService(config$, env, logger);
|
||||
this.configService = new ConfigService(rawConfigProvider, env, logger);
|
||||
|
||||
const core = { coreId, configService: this.configService, env, logger };
|
||||
this.context = new ContextService(core);
|
||||
|
@ -84,6 +91,7 @@ export class Server {
|
|||
const legacyPlugins = await this.legacy.discoverPlugins();
|
||||
|
||||
// Immediately terminate in case of invalid configuration
|
||||
await this.configService.validate();
|
||||
await ensureValidConfiguration(this.configService, legacyPlugins);
|
||||
|
||||
const contextServiceSetup = this.context.setup({
|
||||
|
@ -207,7 +215,7 @@ export class Server {
|
|||
);
|
||||
}
|
||||
|
||||
public async setupConfigSchemas() {
|
||||
public async setupCoreConfig() {
|
||||
const schemas: Array<[ConfigPath, Type<unknown>]> = [
|
||||
[pathConfig.path, pathConfig.schema],
|
||||
[elasticsearchConfig.path, elasticsearchConfig.schema],
|
||||
|
@ -220,6 +228,8 @@ export class Server {
|
|||
[uiSettingsConfig.path, uiSettingsConfig.schema],
|
||||
];
|
||||
|
||||
this.configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider);
|
||||
|
||||
for (const [path, schema] of schemas) {
|
||||
await this.configService.setSchema(path, schema);
|
||||
}
|
||||
|
|
|
@ -25,3 +25,4 @@ export * from './map_to_object';
|
|||
export * from './merge';
|
||||
export * from './pick';
|
||||
export * from './url';
|
||||
export * from './unset';
|
||||
|
|
104
src/core/utils/unset.test.ts
Normal file
104
src/core/utils/unset.test.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { unset } from './unset';
|
||||
|
||||
describe('unset', () => {
|
||||
it('deletes a property from an object', () => {
|
||||
const obj = {
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
c: 'c',
|
||||
};
|
||||
unset(obj, 'a');
|
||||
expect(obj).toEqual({
|
||||
b: 'b',
|
||||
c: 'c',
|
||||
});
|
||||
});
|
||||
|
||||
it('does nothing if the property is not present', () => {
|
||||
const obj = {
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
c: 'c',
|
||||
};
|
||||
unset(obj, 'd');
|
||||
expect(obj).toEqual({
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
c: 'c',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles nested paths', () => {
|
||||
const obj = {
|
||||
foo: {
|
||||
bar: {
|
||||
one: 'one',
|
||||
two: 'two',
|
||||
},
|
||||
hello: 'dolly',
|
||||
},
|
||||
some: {
|
||||
things: 'here',
|
||||
},
|
||||
};
|
||||
unset(obj, 'foo.bar.one');
|
||||
expect(obj).toEqual({
|
||||
foo: {
|
||||
bar: {
|
||||
two: 'two',
|
||||
},
|
||||
hello: 'dolly',
|
||||
},
|
||||
some: {
|
||||
things: 'here',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does nothing if nested paths does not exist', () => {
|
||||
const obj = {
|
||||
foo: {
|
||||
bar: {
|
||||
one: 'one',
|
||||
two: 'two',
|
||||
},
|
||||
hello: 'dolly',
|
||||
},
|
||||
some: {
|
||||
things: 'here',
|
||||
},
|
||||
};
|
||||
unset(obj, 'foo.nothere.baz');
|
||||
expect(obj).toEqual({
|
||||
foo: {
|
||||
bar: {
|
||||
one: 'one',
|
||||
two: 'two',
|
||||
},
|
||||
hello: 'dolly',
|
||||
},
|
||||
some: {
|
||||
things: 'here',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
49
src/core/utils/unset.ts
Normal file
49
src/core/utils/unset.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { get } from './get';
|
||||
|
||||
/**
|
||||
* Unset a (potentially nested) key from given object.
|
||||
* This mutates the original object.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* unset(myObj, 'someRootProperty');
|
||||
* unset(myObj, 'some.nested.path');
|
||||
* ```
|
||||
*/
|
||||
export function unset<OBJ extends { [k: string]: any }>(obj: OBJ, atPath: string) {
|
||||
const paths = atPath
|
||||
.split('.')
|
||||
.map(s => s.trim())
|
||||
.filter(v => v !== '');
|
||||
if (paths.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (paths.length === 1) {
|
||||
delete obj[paths[0]];
|
||||
return;
|
||||
}
|
||||
const property = paths.pop() as string;
|
||||
const parent = get(obj, paths as any) as any;
|
||||
if (parent !== undefined) {
|
||||
delete parent[property];
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ import * as Rx from 'rxjs';
|
|||
import { distinct, toArray, mergeMap, share, shareReplay, filter, last, map, tap } from 'rxjs/operators';
|
||||
import { realpathSync } from 'fs';
|
||||
|
||||
import { transformDeprecations, Config } from '../server/config';
|
||||
import { Config } from '../server/config';
|
||||
|
||||
import {
|
||||
extendConfigService,
|
||||
|
@ -40,9 +40,7 @@ import {
|
|||
} from './errors';
|
||||
|
||||
export function defaultConfig(settings) {
|
||||
return Config.withDefaultSchema(
|
||||
transformDeprecations(settings)
|
||||
);
|
||||
return Config.withDefaultSchema(settings);
|
||||
}
|
||||
|
||||
function bufferAllResults(observable) {
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import { get } from 'lodash';
|
||||
|
||||
import * as serverConfig from '../../server/config';
|
||||
import { getTransform } from '../../deprecation';
|
||||
|
||||
/**
|
||||
|
@ -33,7 +32,7 @@ import { getTransform } from '../../deprecation';
|
|||
*/
|
||||
export async function getSettings(spec, rootSettings, logDeprecation) {
|
||||
const prefix = spec.getConfigPrefix();
|
||||
const rawSettings = get(serverConfig.transformDeprecations(rootSettings), prefix);
|
||||
const rawSettings = get(rootSettings, prefix);
|
||||
const transform = await getTransform(spec);
|
||||
return transform(rawSettings, logDeprecation);
|
||||
}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
const RUN_KBN_SERVER_STARTUP = require.resolve('./fixtures/run_kbn_server_startup');
|
||||
const SETUP_NODE_ENV = require.resolve('../../../../setup_node_env');
|
||||
const SECOND = 1000;
|
||||
|
||||
describe('config/deprecation warnings', function () {
|
||||
this.timeout(65 * SECOND);
|
||||
|
||||
let stdio = '';
|
||||
let proc = null;
|
||||
|
||||
before(async () => {
|
||||
proc = spawn(process.execPath, [
|
||||
'-r', SETUP_NODE_ENV,
|
||||
RUN_KBN_SERVER_STARTUP
|
||||
], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...process.env,
|
||||
CREATE_SERVER_OPTS: JSON.stringify({
|
||||
logging: {
|
||||
quiet: false,
|
||||
silent: false
|
||||
},
|
||||
uiSettings: {
|
||||
enabled: true
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// Either time out in 60 seconds, or resolve once the line is in our buffer
|
||||
return Promise.race([
|
||||
new Promise((resolve) => setTimeout(resolve, 60 * SECOND)),
|
||||
new Promise((resolve, reject) => {
|
||||
proc.stdout.on('data', (chunk) => {
|
||||
stdio += chunk.toString('utf8');
|
||||
if (chunk.toString('utf8').includes('deprecation')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
proc.stderr.on('data', (chunk) => {
|
||||
stdio += chunk.toString('utf8');
|
||||
if (chunk.toString('utf8').includes('deprecation')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('exit', (code) => {
|
||||
proc = null;
|
||||
if (code > 0) {
|
||||
reject(new Error(`Kibana server exited with ${code} -- stdout:\n\n${stdio}\n`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (proc) {
|
||||
proc.kill('SIGKILL');
|
||||
}
|
||||
});
|
||||
|
||||
it('logs deprecation warnings when using outdated config', async () => {
|
||||
const deprecationLines = stdio
|
||||
.split('\n')
|
||||
.map(json => {
|
||||
try {
|
||||
// in dev mode kibana might log things like node.js warnings which
|
||||
// are not JSON, ignore the lines that don't parse as JSON
|
||||
return JSON.parse(json);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter(line =>
|
||||
line.type === 'log' &&
|
||||
line.tags.includes('deprecation') &&
|
||||
line.tags.includes('warning')
|
||||
);
|
||||
|
||||
try {
|
||||
expect(deprecationLines).to.have.length(1);
|
||||
expect(deprecationLines[0]).to.have.property('message', 'uiSettings.enabled is deprecated and is no longer used');
|
||||
} catch (error) {
|
||||
throw new Error(`Expected stdio to include deprecation message about uiSettings.enabled\n\nstdio:\n${stdio}\n\n`);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -17,5 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { transformDeprecations } from './transform_deprecations';
|
||||
export { Config } from './config';
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import _, { set } from 'lodash';
|
||||
import { createTransform, Deprecations } from '../../deprecation';
|
||||
import { unset } from '../../utils';
|
||||
|
||||
const { rename, unused } = Deprecations;
|
||||
|
||||
const savedObjectsIndexCheckTimeout = (settings, log) => {
|
||||
if (_.has(settings, 'savedObjects.indexCheckTimeout')) {
|
||||
log('savedObjects.indexCheckTimeout is no longer necessary.');
|
||||
|
||||
if (Object.keys(settings.savedObjects).length > 1) {
|
||||
delete settings.savedObjects.indexCheckTimeout;
|
||||
} else {
|
||||
delete settings.savedObjects;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const rewriteBasePath = (settings, log) => {
|
||||
if (_.has(settings, 'server.basePath') && !_.has(settings, 'server.rewriteBasePath')) {
|
||||
log(
|
||||
'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' +
|
||||
'will expect that all requests start with server.basePath rather than expecting you to rewrite ' +
|
||||
'the requests in your reverse proxy. Set server.rewriteBasePath to false to preserve the ' +
|
||||
'current behavior and silence this warning.'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const loggingTimezone = (settings, log) => {
|
||||
if (_.has(settings, 'logging.useUTC')) {
|
||||
const timezone = settings.logging.useUTC ? 'UTC' : false;
|
||||
set('logging.timezone', timezone);
|
||||
unset(settings, 'logging.useUTC');
|
||||
log(`Config key "logging.useUTC" is deprecated. It has been replaced with "logging.timezone"`);
|
||||
}
|
||||
};
|
||||
|
||||
const configPath = (settings, log) => {
|
||||
if (_.has(process, 'env.CONFIG_PATH')) {
|
||||
log(`Environment variable CONFIG_PATH is deprecated. It has been replaced with KIBANA_PATH_CONF pointing to a config folder`);
|
||||
}
|
||||
};
|
||||
|
||||
const dataPath = (settings, log) => {
|
||||
if (_.has(process, 'env.DATA_PATH')) {
|
||||
log(`Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`);
|
||||
}
|
||||
};
|
||||
|
||||
const NONCE_STRING = `{nonce}`;
|
||||
// Policies that should include the 'self' source
|
||||
const SELF_POLICIES = Object.freeze(['script-src', 'style-src']);
|
||||
const SELF_STRING = `'self'`;
|
||||
|
||||
const cspRules = (settings, log) => {
|
||||
const rules = _.get(settings, 'csp.rules');
|
||||
if (!rules) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = new Map(rules.map(ruleStr => {
|
||||
const parts = ruleStr.split(/\s+/);
|
||||
return [parts[0], parts.slice(1)];
|
||||
}));
|
||||
|
||||
settings.csp.rules = [...parsed].map(([policy, sourceList]) => {
|
||||
if (sourceList.find(source => source.includes(NONCE_STRING))) {
|
||||
log(`csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`);
|
||||
sourceList = sourceList.filter(source => !source.includes(NONCE_STRING));
|
||||
|
||||
// Add 'self' if not present
|
||||
if (!sourceList.find(source => source.includes(SELF_STRING))) {
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
if (SELF_POLICIES.includes(policy) && !sourceList.find(source => source.includes(SELF_STRING))) {
|
||||
log(`csp.rules must contain the 'self' source. Automatically adding to ${policy}.`);
|
||||
sourceList.push(SELF_STRING);
|
||||
}
|
||||
|
||||
return `${policy} ${sourceList.join(' ')}`.trim();
|
||||
});
|
||||
};
|
||||
|
||||
const deprecations = [
|
||||
//server
|
||||
unused('server.xsrf.token'),
|
||||
unused('uiSettings.enabled'),
|
||||
rename('optimize.lazy', 'optimize.watch'),
|
||||
rename('optimize.lazyPort', 'optimize.watchPort'),
|
||||
rename('optimize.lazyHost', 'optimize.watchHost'),
|
||||
rename('optimize.lazyPrebuild', 'optimize.watchPrebuild'),
|
||||
rename('optimize.lazyProxyTimeout', 'optimize.watchProxyTimeout'),
|
||||
rename('xpack.telemetry.enabled', 'telemetry.enabled'),
|
||||
rename('xpack.telemetry.config', 'telemetry.config'),
|
||||
rename('xpack.telemetry.banner', 'telemetry.banner'),
|
||||
rename('xpack.telemetry.url', 'telemetry.url'),
|
||||
savedObjectsIndexCheckTimeout,
|
||||
rewriteBasePath,
|
||||
loggingTimezone,
|
||||
configPath,
|
||||
dataPath,
|
||||
cspRules
|
||||
];
|
||||
|
||||
export const transformDeprecations = createTransform(deprecations);
|
|
@ -1,182 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { transformDeprecations } from './transform_deprecations';
|
||||
|
||||
describe('server/config', function () {
|
||||
describe('transformDeprecations', function () {
|
||||
describe('savedObjects.indexCheckTimeout', () => {
|
||||
it('removes the indexCheckTimeout and savedObjects properties', () => {
|
||||
const settings = {
|
||||
savedObjects: {
|
||||
indexCheckTimeout: 123,
|
||||
},
|
||||
};
|
||||
|
||||
expect(transformDeprecations(settings)).toEqual({});
|
||||
});
|
||||
|
||||
it('keeps the savedObjects property if it has other keys', () => {
|
||||
const settings = {
|
||||
savedObjects: {
|
||||
indexCheckTimeout: 123,
|
||||
foo: 'bar',
|
||||
},
|
||||
};
|
||||
|
||||
expect(transformDeprecations(settings)).toEqual({
|
||||
savedObjects: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('logs that the setting is no longer necessary', () => {
|
||||
const settings = {
|
||||
savedObjects: {
|
||||
indexCheckTimeout: 123,
|
||||
},
|
||||
};
|
||||
|
||||
const log = sinon.spy();
|
||||
transformDeprecations(settings, log);
|
||||
sinon.assert.calledOnce(log);
|
||||
sinon.assert.calledWithExactly(log, sinon.match('savedObjects.indexCheckTimeout'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('csp.rules', () => {
|
||||
describe('with nonce source', () => {
|
||||
it('logs a warning', () => {
|
||||
const settings = {
|
||||
csp: {
|
||||
rules: [`script-src 'self' 'nonce-{nonce}'`],
|
||||
},
|
||||
};
|
||||
|
||||
const log = jest.fn();
|
||||
transformDeprecations(settings, log);
|
||||
expect(log.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in script-src",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('replaces a nonce', () => {
|
||||
expect(
|
||||
transformDeprecations({ csp: { rules: [`script-src 'nonce-{nonce}'`] } }, jest.fn()).csp
|
||||
.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{ csp: { rules: [`script-src 'unsafe-eval' 'nonce-{nonce}'`] } },
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
).toEqual([`script-src 'unsafe-eval' 'self'`]);
|
||||
});
|
||||
|
||||
it('removes a quoted nonce', () => {
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{ csp: { rules: [`script-src 'self' 'nonce-{nonce}'`] } },
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{ csp: { rules: [`script-src 'nonce-{nonce}' 'self'`] } },
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
});
|
||||
|
||||
it('removes a non-quoted nonce', () => {
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{ csp: { rules: [`script-src 'self' nonce-{nonce}`] } },
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{ csp: { rules: [`script-src nonce-{nonce} 'self'`] } },
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
});
|
||||
|
||||
it('removes a strange nonce', () => {
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{ csp: { rules: [`script-src 'self' blah-{nonce}-wow`] } },
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
).toEqual([`script-src 'self'`]);
|
||||
});
|
||||
|
||||
it('removes multiple nonces', () => {
|
||||
expect(
|
||||
transformDeprecations(
|
||||
{
|
||||
csp: {
|
||||
rules: [
|
||||
`script-src 'nonce-{nonce}' 'self' blah-{nonce}-wow`,
|
||||
`style-src 'nonce-{nonce}' 'self'`,
|
||||
],
|
||||
},
|
||||
},
|
||||
jest.fn()
|
||||
).csp.rules
|
||||
).toEqual([`script-src 'self'`, `style-src 'self'`]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without self source', () => {
|
||||
it('logs a warning', () => {
|
||||
const log = jest.fn();
|
||||
transformDeprecations({ csp: { rules: [`script-src 'unsafe-eval'`] } }, log);
|
||||
expect(log.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"csp.rules must contain the 'self' source. Automatically adding to script-src.",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('adds self', () => {
|
||||
expect(
|
||||
transformDeprecations({ csp: { rules: [`script-src 'unsafe-eval'`] } }, jest.fn()).csp
|
||||
.rules
|
||||
).toEqual([`script-src 'unsafe-eval' 'self'`]);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not add self to other policies', () => {
|
||||
expect(
|
||||
transformDeprecations({ csp: { rules: [`worker-src blob:`] } }, jest.fn()).csp.rules
|
||||
).toEqual([`worker-src blob:`]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -31,8 +31,6 @@ import { loggingMixin } from './logging';
|
|||
import warningsMixin from './warnings';
|
||||
import { statusMixin } from './status';
|
||||
import pidMixin from './pid';
|
||||
import { configDeprecationWarningsMixin } from './config/deprecation_warnings';
|
||||
import { transformDeprecations } from './config/transform_deprecations';
|
||||
import configCompleteMixin from './config/complete';
|
||||
import optimizeMixin from '../../optimize';
|
||||
import * as Plugins from './plugins';
|
||||
|
@ -89,7 +87,6 @@ export default class KbnServer {
|
|||
// adds methods for extending this.server
|
||||
serverExtensionsMixin,
|
||||
loggingMixin,
|
||||
configDeprecationWarningsMixin,
|
||||
warningsMixin,
|
||||
statusMixin,
|
||||
|
||||
|
@ -198,7 +195,7 @@ export default class KbnServer {
|
|||
applyLoggingConfiguration(settings) {
|
||||
const config = new Config(
|
||||
this.config.getSchema(),
|
||||
transformDeprecations(settings)
|
||||
settings
|
||||
);
|
||||
|
||||
const loggingOptions = loggingConfiguration(config);
|
||||
|
|
|
@ -41,6 +41,11 @@ export const config: PluginConfigDescriptor = {
|
|||
uiProp: true,
|
||||
},
|
||||
schema: configSchema,
|
||||
deprecations: ({ rename, unused, renameFromRoot }) => [
|
||||
rename('securityKey', 'secret'),
|
||||
renameFromRoot('oldtestbed.uiProp', 'testbed.uiProp'),
|
||||
unused('deprecatedProperty'),
|
||||
],
|
||||
};
|
||||
|
||||
class Plugin {
|
||||
|
|
|
@ -33,7 +33,6 @@ import { resolve } from 'path';
|
|||
import { BehaviorSubject } from 'rxjs';
|
||||
import supertest from 'supertest';
|
||||
import { CliArgs, Env } from '../core/server/config';
|
||||
import { LegacyObjectToConfigAdapter } from '../core/server/legacy';
|
||||
import { Root } from '../core/server/root';
|
||||
import KbnServer from '../legacy/server/kbn_server';
|
||||
import { CallCluster } from '../legacy/core_plugins/elasticsearch';
|
||||
|
@ -84,9 +83,9 @@ export function createRootWithSettings(
|
|||
});
|
||||
|
||||
return new Root(
|
||||
new BehaviorSubject(
|
||||
new LegacyObjectToConfigAdapter(defaultsDeep({}, settings, DEFAULTS_SETTINGS))
|
||||
),
|
||||
{
|
||||
getConfig$: () => new BehaviorSubject(defaultsDeep({}, settings, DEFAULTS_SETTINGS)),
|
||||
},
|
||||
env
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue