Skip to content

Implements config deprecation in New Platform#52251

Merged
pgayvallet merged 33 commits intoelastic:masterfrom
pgayvallet:kbn-40255-NP-config-deprecation
Dec 13, 2019
Merged

Implements config deprecation in New Platform#52251
pgayvallet merged 33 commits intoelastic:masterfrom
pgayvallet:kbn-40255-NP-config-deprecation

Conversation

@pgayvallet
Copy link
Copy Markdown
Contributor

@pgayvallet pgayvallet commented Dec 5, 2019

Summary

Fix #40255

Implement configuration deprecation management in new platform. core and plugins can now register deprecations to be used to mutates the raw configuration (by renaming or removing deprecated properties)

Actual implementation

The legacy config deprecation system was chaotic in the sense that deprecations were actually applied in different points (and time) in the code. However, the underneath implementation and API was pretty clever and NP implementation is strongly inspired by it.

core can now register deprecation providers using configService.addDeprecationProvider. Plugins can register the same providers when declaring their configuration using their PluginConfigDescriptor

 const configSchema = schema.object({
   someNewKey: schema.string({ defaultValue: 'a string' }),
 });
 
 type ConfigType = TypeOf<typeof configSchema>;
 
 export const config: PluginConfigDescriptor<ConfigType> = {
   schema: configSchema,
   deprecations: ({ rename, unused }) => [
     rename('someOldKey', 'someNewKey'),
     unused('deprecatedProperty'),
   ],
 };

By default, deprecations can only access a plugin's configuration schema, meaning that rename('someOldKey', 'someNewKey'), will actually rename myplugin.someOldKey to myplugin.someNewKey. However due to legacy and edge case requirements, we also expose APIs to access the whole config (renameFromRoot and unusedFromRoot)

Deprecations are not limited to rename and unused (even if they cover 99% of the needs) and any custom implementation of ConfigDeprecation can be returned by a provider.

RawConfigService and ConfigService changes

To properly manage and apply deprecations to the configuration values, I had to change these two services responsibilities a little. Now the RawConfigService returns the configuration values in a raw, Record<string, any> format instead of returning an actual Config object. This allows to move all configuration data manipulation logic in a single place (the config service). This is probably more coherent regarding what we could expect of a Raw[..]-type service anyway.

The most important change regarding ConfigService is in the schema validation timing: because of some edge cases of deprecations, it's no longer possible to validates a plugin's schema when registering it. Practical exemple is a property that would have been migrated from one plugin to another (and we got some of these in the legacy deprecations, such as rename('xpack.telemetry.enabled', 'telemetry.enabled'),). For this reason, global config validation is now done at a single point of time, just after plugins discovery.

Legacy deprecations in new platform

As legacy and new deprecation system are pretty close, I was able to create an adapter from legacy deprecations to new ones. the LegacyService now collect every legacy plugin's deprecation providers, adapt them, and register them in the new platform. That way, the NP configuration is properly aware of legacy deprecations and expose up to date values.

What remains to be done

  • Totally removes the legacy deprecation system

UPDATE after discussion, this will be done after NP plugin migration, and is out of the scope of this PR

We are so close and yet so far. This impacts LegacyService and legacy code the most. Currently, the legacy service creates a LegacyConfig from the raw config's value, and calls findPluginSpecs with it. The config then enter in the legacy world/code, where discovered legacy plugins mutates the legacy configuration with both their schema, their value and their deprecations. This mutated legacy config is then considered the source of truth when we instanciate the legacy kibana server.

To be able to totally suppress the legacy deprecation system, we will need to remove the part where the deprecation are applied from legacy findPluginSpecs, get the non-migrated config back in legacyService, and then apply the new platform deprecation to it. However, all this code is strongly coupled atm, not typed (js, no ts), and lack test coverage, so I will need to be extremely careful with that part, and think that we might want to do it in a separate PR.

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

- [ ] This was checked for cross-browser compatibility, including a check against IE11
- [ ] Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support

For maintainers

- [ ] This was checked for breaking API changes and was labeled appropriately

Dev Docs

New platform plugin's configuration now supports deprecation. Use the deprecations key of a plugin's config descriptor to register them.

 // my_plugin/server/index.ts
 import { schema, TypeOf } from '@kbn/config-schema';
 import { PluginConfigDescriptor } from 'kibana/server';
 
 const configSchema = schema.object({
   someNewKey: schema.string({ defaultValue: 'a string' }),
 });
 
 type ConfigType = TypeOf<typeof configSchema>;
 
 export const config: PluginConfigDescriptor<ConfigType> = {
   schema: configSchema,
   deprecations: ({ rename, unused }) => [
     rename('someOldKey', 'someNewKey'),
     unused('deprecatedProperty'),
   ],
 };

@elasticmachine

This comment has been minimized.

@elasticmachine

This comment has been minimized.

@elasticmachine

This comment has been minimized.

@pgayvallet pgayvallet force-pushed the kbn-40255-NP-config-deprecation branch from 98bcb38 to 31a9c38 Compare December 6, 2019 11:15
@elasticmachine

This comment has been minimized.

@elasticmachine
Copy link
Copy Markdown
Contributor

💚 Build Succeeded

@pgayvallet pgayvallet added Feature:New Platform release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. Team:Core Platform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t// v7.6.0 v8.0.0 labels Dec 6, 2019
@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/kibana-platform (Team:Platform)

Comment on lines +44 to +48
deprecations: ({ rename, unused, renameFromRoot }) => [
rename('securityKey', 'secret'),
renameFromRoot('oldtestbed.uiProp', 'testbed.uiProp'),
unused('deprecatedProperty'),
],
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an example in testbed. Not sure if I should keep it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could add something similar to the integration tests in LP https://github.com/restrry/kibana/blob/55604f711f2b06ca58235c1aabc45f5281e13642/src/legacy/server/config/__tests__/deprecation_warnings.js#L28
although it tests only logging part

Copy link
Copy Markdown
Contributor Author

@pgayvallet pgayvallet Dec 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a similar approach to what LP does, by actually spawning a real kibana process? Could not find a NP equivalent to legacy const RUN_KBN_SERVER_STARTUP = require.resolve('./fixtures/run_kbn_server_startup'); ? Seems like more than what we call integration_test in NP?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run_kbn_server_startup uses test_utils under the hood. So do we in the integrations tests.
https://github.com/restrry/kibana/blob/55604f711f2b06ca58235c1aabc45f5281e13642/src/core/server/http/integration_tests/core_services.test.ts#L23
so yes, I think we can adopt the same approach

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, missed that.

@pgayvallet pgayvallet marked this pull request as ready for review December 6, 2019 13:38
@pgayvallet pgayvallet requested a review from a team as a code owner December 6, 2019 13:38
@joshdover
Copy link
Copy Markdown
Contributor

Overall this is looking great. Haven't dug deep into the implementation yet, but the interface and design looks pretty good to me.

  • Totally removes the legacy deprecation system

We are so close and yet so far. This impacts LegacyService and legacy code the most. Currently, the legacy service creates a LegacyConfig from the raw config's value, and calls findPluginSpecs with it. The config then enter in the legacy world/code, where discovered legacy plugins mutates the legacy configuration with both their schema, their value and their deprecations. This mutated legacy config is then considered the source of truth when we instanciate the legacy kibana server.

To be able to totally suppress the legacy deprecation system, we will need to remove the part where the deprecation are applied from legacy findPluginSpecs, get the non-migrated config back in legacyService, and then apply the new platform deprecation to it. However, all this code is strongly coupled atm, not typed (js, no ts), and lack test coverage, so I will need to be extremely careful with that part, and think that we might want to do it in a separate PR.

Just so I understand correctly:

As-is in this PR right now, New Platform plugins and Legacy plugins will be reading different configs because the NP deprecations are not applied to the config that legacy reads. However, the legacy deprecations are applied to the config that NP reads. Correct?

If so, are we sure there's a need to apply NP deprecations to the config that LP reads? It seems that if an LP plugin wants to read a renamed config key, it'd be fine if we told them they still need to implement the deprecation in the LP too.

@pgayvallet
Copy link
Copy Markdown
Contributor Author

As-is in this PR right now, New Platform plugins and Legacy plugins will be reading different configs because the NP deprecations are not applied to the config that legacy reads. However, the legacy deprecations are applied to the config that NP reads. Correct?

That is correct.

If so, are we sure there's a need to apply NP deprecations to the config that LP reads? It seems that if an LP plugin wants to read a renamed config key, it'd be fine if we told them they still need to implement the deprecation in the LP too.

I don't know if there is a functional need. To be honest I'm not even sure we really should do it.

However what I meant what more about technical cleanup: ATM, legacy deprecations are applied to the legacy config (a good thing) but in the legacy world (which is the issue), therefor blocking me from actually remove all the legacy config deprecation system. Now that legacy deprecation can be adapted to NP one, we could apply the adapted legacy deprecations (and maybe the NP ones) to LP config in NP (probably in the legacy service), which would allow us to get rid of the legacy code now without waiting for post 8.0.

But we can also decide that this is not necessary and just keep this legacy code until we actually unplug legacy.

WDYT?

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"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we remove core deprecations from the legacy now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, and I'm not sure of the answer to be honest.

There is one deprecation I did not migrate yet from src/legacy/server/config/transform_deprecations.js, the one regarding CSP rules. in legacy, the csp config is located at csp. In #52161, It seems we moved it to server.csp, so I was not sure what to do about it.

But I could at least remove all the others from legacy I think

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I migrated the csp deprecations. Btw the logic inside it looks like more like actual validation that deprecation, we might want to move it to config validation at some point.

That was the last thing blocking, I should be able to suppress src/legacy/server/config/transform_deprecations.js and it's usages now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b9e0c6c removes the legacy core deprecations.

expect(logs.warn).toMatchInlineSnapshot(`
Array [
Array [
"\\"optimize.lazy\\" is deprecated and has been replaced by \\"optimize.watch\\"",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a disadvantage of such testing is lack of context testing. (config.deprecation)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. However I think I would prefer to improve the logger mock to allow to filter by context than actually use child_process.spawn is possible, as we may also want it in unit test (and I already do in this PR for the added test in src/core/server/server.test.ts). However this is a big change and I would prefer to do it in a separate PR.

But WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we could leverage new config format for NP logging #41956 to filter logs by context and write them into inMemoryAppender. That would require minimal boilerplate code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BufferAppender you mean?

  • It is a reserved appender that can't be set via configuration file
  • It will still requires some mocking or trick to access the appender logs, as it's not exposed currently

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't mean the concrete implementation. We can implement a custom appender for testing purposes only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in src/core/server/logging/appenders/appenders.ts make me think that this would mean ship test-only code, as there is currently no way to register custom appenders (and I don't think we planned/want that?), so we would still relies on some mocking mechanism (to mock Appenders.create for example), so not sure of the added value in comparison to put this logging into the logger mock?

But I think testing tools in integration tests (aka when using kbnTestServer) is a discussion on it's own.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and I don't think we planned/want that?

I have no clue to what extent we want to support log4j2 configuration. It was just an idea of testing the whole logging pipeline (including filtering & appenders).

@joshdover
Copy link
Copy Markdown
Contributor

It's not worth to do it now. Removing it after plugins migration is safer and way less complicated.

👍

@joshdover joshdover mentioned this pull request Dec 11, 2019
7 tasks
@mshustov mshustov mentioned this pull request Dec 11, 2019
3 tasks
* under the License.
*/

import _, { has, get } from 'lodash';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import _, { has, get } from 'lodash';
import { has, get } from 'lodash';

return new ClusterManager(
opts,
Config.withDefaultSchema(transformDeprecations(settings)),
Config.withDefaultSchema(settings),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it works as expected. As I understand settings is expected to be POJO here. But we pass LegacyConfig https://github.com/elastic/kibana/pull/52251/files#diff-748376502c51ed8bfc271f4a6c4c26d3R251
Shouldn't we wrap in getLegacyRawConfig or pass this.settings in legacy_service ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right. However the PR did not change the arguments we were invoking ClusterManager.create with. So I guess this has always been broken?

The fix is (well, should be) easy

require('../../../cli/cluster/cluster_manager').create(
      this.coreContext.env.cliArgs,
--    config,
++    config.get(),
      await basePathProxy$.toPromise()
    );

However the legacy service tests are a mess, and are not properly mocking with correct object types (the config in test is actually a Record and not a LegacyConfig ). Already had to fix that in #52060, so would be easier to fix in a separate PR once it lands.

Will open a ticket for that if it's alright with you

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#52860 created

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, not a blocker for the current work.

[uiSettingsConfig.path, uiSettingsConfig.schema],
];

this.configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we rename setupConfigSchemas ? for example setupCoreConfig.

@elasticmachine
Copy link
Copy Markdown
Contributor

💚 Build Succeeded

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pgayvallet pgayvallet merged commit eb3d9b1 into elastic:master Dec 13, 2019
pgayvallet added a commit to pgayvallet/kibana that referenced this pull request Dec 13, 2019
* 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
pgayvallet added a commit that referenced this pull request Dec 13, 2019
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature:New Platform release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. Team:Core Platform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t// v7.6.0 v8.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New Platform Config service should support deprecations

4 participants