Move base feature controls functionality from XPack Main plugin to a dedicated XPack Features plugin#44664
Conversation
|
Pinging @elastic/kibana-platform |
|
Pinging @elastic/kibana-security |
| /src/legacy/server/saved_objects/ @elastic/kibana-platform | ||
| /src/legacy/ui/public/saved_objects @elastic/kibana-platform | ||
| /config/kibana.yml @elastic/kibana-platform | ||
| /x-pack/plugins/features/ @elastic/kibana-platform |
There was a problem hiding this comment.
note: Platform team will own this plugin as soon as we merge it.
docs/api/features/get.asciidoc
Outdated
|
|
||
| To retrieve all features, issue a GET request to the | ||
| /api/features/v1 endpoint. | ||
| /features/api endpoint. |
There was a problem hiding this comment.
note: it feels like not a big deal to change URL in a minor for this particular API.
There was a problem hiding this comment.
I agree, this is a negligible change. Is the /${pluginId}/api format a new standard? I know that endpoints have to be prefixed with the plugin id as part of the NP migration, but the /api suffix feels strange to me. Reading this, I'd expect this to retrieve a feature with id api, instead of retrieving all registered features.
We had a similar problem when we wrote the spaces api, but I'm not thrilled with what we did there either...
/api/spaces/space retrieves all spaces
/api/spaces/space/foo retrieves a space with id foo.
We don't need the latter for this API call, since we don't support retrieving a single feature by id. If we follow the spaces pattern though (updating for the NP), we get something like:
/features/feature
...which isn't great either.
Maybe
/features/api/feature?
I'm more than open to other suggestions
There was a problem hiding this comment.
Is the /${pluginId}/api format a new standard?
It's not clear yet, that's why I'd like to discuss it here and in #44620. I need to introduce NP routes in a couple PRs right now and was thinking whether we should separate API and view routes somehow, that's how this /api/ part appeared.
I'd expect this to retrieve a feature with id api, instead of retrieving all registered features.
Heh, that's a good point!
Maybe /features/api/feature?
Yeah, or something verbose like /features/api/all-features, /features/api/feature/{id}, still thinking, don't have good enough ideas yet....
I was also thinking about features/_api/, _features/api/, features.api/ to kind of stress on the difference between these two semantically different parts of the URL. We can also say that features/* are always API calls and features/{views|app|pages}/* are for {views|app|pages}.
@restrry it's probably a good time to pick some API naming style that we can follow :) Did you guys had a chance to discuss that already?
There was a problem hiding this comment.
Once #44855 is merged I'll change URL from /api/features/v1 to /api/features.
There was a problem hiding this comment.
Changed to /api/features
| @@ -0,0 +1,8 @@ | |||
| { | |||
There was a problem hiding this comment.
note: we need to get access to the timelion.ui.enabled setting from the Features plugin so introducing NP timelion plugin felt natural to me, but I'm open to any better ideas!
| "id": "features", | ||
| "version": "8.0.0", | ||
| "kibanaVersion": "kibana", | ||
| "optionalPlugins": ["timelion"], |
There was a problem hiding this comment.
note: just because we want to know value of timelion.ui.enabled.
| export class Plugin { | ||
| private readonly logger: Logger; | ||
|
|
||
| private legacyAPI?: LegacyAPI; |
There was a problem hiding this comment.
note: same approach we used in NP Security plugin, but happy to hear any better suggestions.
| return deepFreeze({ | ||
| registerFeature: featureRegistry.register.bind(featureRegistry), | ||
| getFeatures: featureRegistry.getAll.bind(featureRegistry), | ||
| getFeaturesUICapabilities: () => uiCapabilitiesForFeatures(featureRegistry.getAll()), |
There was a problem hiding this comment.
question: I expose this just because it's used in uiCapabilities method of the XPack Main Plugin definition. Josh do we have anything like this in NP world so that I can use it here and don't expose to LP?
There was a problem hiding this comment.
On the server-side, the NP doesn't have anything related to UI capabilities yet.
The ApplicationService on the client-side does currently expose the injected ui capabilities for the current user. We will change this to fetch UI capabilities (#36319) based on the registered applications once security's HTTP interceptors are moved over.
Whenever the fetch endpoint for this is moved over to the New Platform, I expect the New Platform will need to expose a way for this plugin to inject additional capabilities. We can make that change at a later time.
| "common.ui.savedObjects.overwriteRejectedDescription": "上書き確認が拒否されました", | ||
| "common.ui.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存", | ||
| "common.ui.savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました", | ||
| "kibana-react.savedObjects.saveModal.cancelButtonLabel": "キャンセル", |
There was a problem hiding this comment.
note: this has been automatically rearranged by i18n script.
There was a problem hiding this comment.
Would it be worthwhile to find/replace xpack.main.featureRegistry.* with xpack.features.* in the translated files, since we know that the translated text doesn't need to change? Or is that not advised?
There was a problem hiding this comment.
We never did this since translators are supposed to receive en.json for every release, and in this case these labels will be recognized as already translated even though IDs changed (so that we're not charged additionally). But let's double check with @Bamieh
There was a problem hiding this comment.
Currently we send a json diff file in english and one for every language we have, so renaming these ID's will be synced with translators. We started doing this to sync translations introduced in PRs as we used to ignore them.
I'd vote for renaming these ids as it will also help keep kibana translated even before we sync with the translators which only happens a few days before every FF release.
There was a problem hiding this comment.
Okay, will do that than. I usually advised against manual edits in files with translations and treated them almost as "auto-generated" as it's easy to make something wrong there :)
| if (!request.path.startsWith('/api/') || !mode.useRbacForRequest(request)) { | ||
| // if the api doesn't include or end with "/api/" or we aren't using RBAC for this request, | ||
| // just continue | ||
| if ( |
There was a problem hiding this comment.
note: temp fix, we need to discuss what to do here with plugin-id prefixed NP API routes.
There was a problem hiding this comment.
I'm trying to remember why we had the startsWith('/api/') check to begin with...I don't think it's necessary for us to check for that path part in addition to the existence of the access:foo tag. We should support securing any route with an appropriately configured access: tag, even if it doesn't have /api/ within its path.
This may have just been a holdover from an earlier implementation for securing API endpoints - @kobelb do you remember differently?
There was a problem hiding this comment.
Reverted this change since we can keep /api/features now.
💔 Build Failed |
💚 Build Succeeded |
|
@joshdover @legrego would really appreciate your feedback on this PR (will tag you for review)! |
|
ACK will review today/tomorrow |
legrego
left a comment
There was a problem hiding this comment.
Made a first pass at the code today (mostly questions) -- I'll test this out some more tomorrow, but I wanted to give you something to look at before my day starts
docs/api/features/get.asciidoc
Outdated
|
|
||
| To retrieve all features, issue a GET request to the | ||
| /api/features/v1 endpoint. | ||
| /features/api endpoint. |
There was a problem hiding this comment.
I agree, this is a negligible change. Is the /${pluginId}/api format a new standard? I know that endpoints have to be prefixed with the plugin id as part of the NP migration, but the /api suffix feels strange to me. Reading this, I'd expect this to retrieve a feature with id api, instead of retrieving all registered features.
We had a similar problem when we wrote the spaces api, but I'm not thrilled with what we did there either...
/api/spaces/space retrieves all spaces
/api/spaces/space/foo retrieves a space with id foo.
We don't need the latter for this API call, since we don't support retrieving a single feature by id. If we follow the spaces pattern though (updating for the NP), we get something like:
/features/feature
...which isn't great either.
Maybe
/features/api/feature?
I'm more than open to other suggestions
| import { first } from 'rxjs/operators'; | ||
| import { TypeOf } from '@kbn/config-schema'; | ||
| import { PluginInitializerContext, RecursiveReadonly } from '../../../../src/core/server'; | ||
| import { deepFreeze } from '../../../../src/core/utils'; |
There was a problem hiding this comment.
I didn't know we could import from src/core/utils. Last time I tried within x-pack, it was disallowed/not working. Is this something we support for x-pack plugins too, or is it just for OSS plugins?
There was a problem hiding this comment.
Is this something we support for x-pack plugins too, or is it just for OSS plugins?
We use it for Security plugin already, so it should be supported: https://github.com/elastic/kibana/blob/master/x-pack/plugins/security/server/plugin.ts#L16
| * you may not use this file except in compliance with the Elastic License. | ||
| */ | ||
|
|
||
| import Joi from 'joi'; |
There was a problem hiding this comment.
Question: Is joi still the validation framework of choice, or should we be using @kbn/config-schema?
There was a problem hiding this comment.
I just kept what we had before, but I believe we should eventually migrate to @kbn/config-schema assuming everything we have in these schemas is supported by @kbn/config-schema
There was a problem hiding this comment.
Yeah I think it's fine if we change this later.
|
|
||
| import Joi from 'joi'; | ||
|
|
||
| // Each feature gets its own property on the UICapabilities object, |
There was a problem hiding this comment.
nit: I think this comment belongs with line 15 below.
There was a problem hiding this comment.
Ha, right, thanks!
| } | ||
|
|
||
| /** | ||
| * Describes a set of APIs that is available in the legacy platform only and required by this plugin |
There was a problem hiding this comment.
nit
| * Describes a set of APIs that is available in the legacy platform only and required by this plugin | |
| * Describes a set of APIs that are available in the legacy platform only and required by this plugin |
There was a problem hiding this comment.
Fixed! Out of curiosity and to improve my grammar skills: does *set* .... that *is* .... sound too unnatural comparing to set of *things* that *are*? :)
| Logger, | ||
| PluginInitializerContext, | ||
| RecursiveReadonly, | ||
| } from '../../../../src/core/server'; |
There was a problem hiding this comment.
Should we be using the from 'src/core/server' alias instead of the relative import?
There was a problem hiding this comment.
I believe I heard somewhere that we should use relative paths instead of this alias when I was working Security NP plugin since it's exactly same approach we'd use to import from other plugins, but maybe something has changed since then. @elastic/kibana-platform what would be the correct approach to import from the core?
There was a problem hiding this comment.
Ah ok, I'm interested to see what Platform says here. I'm ok with relative imports if that's the direction we want to go, but third-party plugins wouldn't be able to take advantage of that, right?
There was a problem hiding this comment.
I see the relevant discussion is still happening here: #40446 (comment)
| registerLegacyAPI: (legacyAPI: LegacyAPI) => { | ||
| this.legacyAPI = legacyAPI; | ||
|
|
||
| // Register OSS features. |
There was a problem hiding this comment.
Are we registering OSS features here because we need the saved object types from the legacy API? It was a little confusing when I first read this, as it feels out of place right now. I'm OK leaving it as-is since it's temporary.
There was a problem hiding this comment.
Are we registering OSS features here because we need the saved object types from the legacy API?
Yep. And it's confusing, definitely agree. Initially I had additional setup contract registerOSSFeatures method, but it was also confusing as "something" that is outside of Features control is supposed to initialize Features plugin at "some" point 🤷♂️
Hopefully we'll get SO on server side in NP soon and get rid of this hack.
| "common.ui.savedObjects.overwriteRejectedDescription": "上書き確認が拒否されました", | ||
| "common.ui.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存", | ||
| "common.ui.savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました", | ||
| "kibana-react.savedObjects.saveModal.cancelButtonLabel": "キャンセル", |
There was a problem hiding this comment.
Would it be worthwhile to find/replace xpack.main.featureRegistry.* with xpack.features.* in the translated files, since we know that the translated text doesn't need to change? Or is that not advised?
| // functions or removal of exports should be considered as a breaking change. Ideally we should | ||
| // reduce number of such exports to zero and provide everything we want to expose via Setup/Start | ||
| // run-time contracts. | ||
| export { uiCapabilitiesRegex } from './feature_schema'; |
There was a problem hiding this comment.
For what it's worth, we should probably move uiCapabilitiesRegex into src/core, as UI Capabilities are a part of OSS now. You can leave it here for the time being, but if we're concerned about API surface, that's one way to reduce it 😄.
question: your comment about reducing these exports to zero doesn't include the types we export below, right? We can still export types from our plugins outside of the lifecycle methods?
There was a problem hiding this comment.
For what it's worth, we should probably move uiCapabilitiesRegex into src/core, as UI Capabilities are a part of OSS now. You can leave it here for the time being, but if we're concerned about API surface, that's one way to reduce it
Yeah, I tried to do that several times while working on this, but failed to do so every time 😆 I just can't find a proper place for it, it was kind of confusing that it sits in the core, but is not used there at all and it's not clear how exactly it's used while reading core code. If you can help me to find a justification I'll happily move that to the core 🙂
There was a problem hiding this comment.
question: your comment about reducing these exports to zero doesn't include the types we export below, right? We can still export types from our plugins outside of the lifecycle methods?
Correct!
There was a problem hiding this comment.
Yeah, I tried to do that several times while working on this, but failed to do so every time 😆 I just can't find a proper place for it, it was kind of confusing that it sits in the core, but is not used there at all and it's not clear how exactly it's used while reading core code. If you can help me to find a justification I'll happily move that to the core 🙂
It doesn't exist today, but I could see this being useful when we build the list of capabilities. Currently, plugins can provide any capabilities they'd like, even if they violate the regex. We're just trusting that they don't right now.
It's potentially also useful when we resolve capabilities. Core might want to prevent registered Capabilities Modifiers from returning a capability which violates the regex. In theory, the modifiers shouldn't be adding or removing any capabilities; rather, they should just be flipping the boolean flags...
There was a problem hiding this comment.
Ah, good to know, thanks. Yeah, it makes a lot of sense to have it in core then.
There was a problem hiding this comment.
Moved to the core.
There was a problem hiding this comment.
Nah, had to revert and move back to the Features plugin. There is something weird happening when we require regex from ui/capabilities in server code. It's probably expected, but I'll let platform team to handle that when they want to move that to core :)
|
@joshdover @legrego thanks for the feedback! I think I've handled everything you mentioned so far, so PR is ready for another pass. |
This comment has been minimized.
This comment has been minimized.
💔 Build Failed |
💚 Build Succeeded |
legrego
left a comment
There was a problem hiding this comment.
Everything seems to be working great! Just a doc update needed from my perspective.
| xpackMainPlugin.registerFeature({ | ||
| id: 'dev_tools', | ||
| name: i18n.translate('xpack.main.featureRegistry.devToolsFeatureName', { | ||
| name: i18n.translate('xpack.features.devToolsFeatureName', { |
There was a problem hiding this comment.
This file has a few references to the legacy filepath that we'll want to update:
💔 Build Failed |
Looks like unrelated issue. |
|
retest |
💚 Build Succeeded |
joshdover
left a comment
There was a problem hiding this comment.
LGTM, thanks @azasypkin!
💚 Build Succeeded |
|
7.x/7.5.0: 6341714 |
We're moving Security Authorization system to the New Platform and it depends on the "features" functionality. To minimize a number of dependencies on the Legacy Platform plugins we're introducing "Features" plugin that will solely responsible for the base Feature Controls functionality.
NOTE: it's easier to review this PR commit by commit because of
git mv. Not much of new code is written here, I just moved some code and split some large files into smaller ones.Blocked by: #44855