-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change saved object bulkUpdate to work across multiple namespaces #75478
Change saved object bulkUpdate to work across multiple namespaces #75478
Conversation
98ee331
to
1059ecc
Compare
Now that saved objects' `namespaces` are exposed, we should provide a method to compare these strings to namespace IDs. The Spaces plugin already provided utility functions for this; I changed them to be a facade over the new core functions. The reason for this is that other plugins (alerting, actions) depend on the Spaces plugin and will use an `undefined` namespace if the Spaces plugin is not enabled.
1059ecc
to
fa6ae1c
Compare
Includes docs changes and integration tests.
863811a
to
341052a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Author's notes for reviewers.
Note: I did not change the developer docs, because there are none for bulkUpdate (even though there are docs for docs/api/saved-objects/bulk_create.asciidoc
and docs/api/saved-objects/bulk_get.asciidoc
, for example!) I think creating brand new docs for bulkUpdate is out of scope for this PR.
export const namespaceStringToId = (namespace: string) => { | ||
if (!namespace) { | ||
throw new TypeError('namespace must be a non-empty string'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a new error case that may be encountered when using the saved objects client now. That said, it should never happen organically, so I'm not sure we need to be concerned about it. Thoughts?
If we remove the error check here:
- Single-namespace object types would be treated as if they are in the default space
- Multi-namespace object types would be treated as if they are in the
''
space
I don't like the difference in behavior, so I'm in favor of leaving the error check in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm +1 to keeping the error check here
export function spaceIdToNamespace(spaceId: string): string | undefined { | ||
if (!spaceId) { | ||
throw new TypeError('spaceId is required'); | ||
} | ||
|
||
if (spaceId === DEFAULT_SPACE_ID) { | ||
return undefined; | ||
} | ||
|
||
return spaceId; | ||
/** | ||
* Converts a Space ID string to its namespace ID representation. Note that a Space ID string is equivalent to a namespace string. | ||
* | ||
* See also: {@link namespaceStringToId}. | ||
*/ | ||
export function spaceIdToNamespace(spaceId: string) { | ||
return namespaceStringToId(spaceId); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Spaces plugin exposes the spaceIdToNamespace
method (and namespaceToSpaceId
below). These are functionally identical to the new namespaceStringToId
and namespaceIdToString
methods in OSS.
Instead of changing all usages of these to the new OSS method, I left them as-is and converted them into a facade. My primary reasoning for this is that the Actions and Alerting plugins are checking to see if the Spaces plugin is enabled before using these; if it is not enabled, they fall back to the default undefined
namespace.
It's also worth noting here that our nomenclature is a bit confusing:
- "namespace ID" is either a non-empty string, or undefined
- "namespace string" is a non-empty string
- "space ID" is a non-empty string, and is identical to "namespace string"
I think "namespace ID" and "namespace string" make more sense, but "space ID" has been around for a long time now and it would be painful to change. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a perfect world, the concept of a "namespace" would have never leaked out of the saved objects system. It was always meant to be an implementation detail of the Spaces feature, so my vote is to keep "Space ID".
I also agree with your decision to keep these functions around, even though they are functionally equivilent today.
return { | ||
...(jest.requireActual('../../../../../../src/core/server') as Record<string, unknown>), | ||
exportSavedObjectsToStream: jest.fn(), | ||
importSavedObjectsFromStream: jest.fn(), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I'm exporting more methods from core, this mock was broken. Added a spread of requireActual
to fix it (and two other unit tests as well).
await this.ensureAuthorized( | ||
this.getUniqueObjectTypes(objects), | ||
'bulk_update', | ||
options && options.namespace, | ||
{ objects, options } | ||
); | ||
const objectNamespaces = objects | ||
.filter(({ namespace }) => namespace !== undefined) | ||
.map(({ namespace }) => namespace!); | ||
const namespaces = uniq([namespaceIdToString(options?.namespace), ...objectNamespaces]); | ||
await this.ensureAuthorized(this.getUniqueObjectTypes(objects), 'bulk_update', namespaces, { | ||
objects, | ||
options, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of just doing an authorization check for options.namespace
, do one for options.namespace
and all of the individual "object namespaces" (if any are defined).
Note that we might not need to do an authorization check foroptions.namespace
(if all objects specify an individual "object namespace"), but I left this here for consistency. It would be weird to be able to call bulkUpdate in a space that you don't have access to, and only update objects in other spaces.
@elasticmachine merge upstream |
ACK: Started review today, hoping to finish tomorrow. I need to mull this one over a bit, but there's nothing critically "wrong" here 😄 |
export const namespaceStringToId = (namespace: string) => { | ||
if (!namespace) { | ||
throw new TypeError('namespace must be a non-empty string'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm +1 to keeping the error check here
x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts
Outdated
Show resolved
Hide resolved
export function spaceIdToNamespace(spaceId: string): string | undefined { | ||
if (!spaceId) { | ||
throw new TypeError('spaceId is required'); | ||
} | ||
|
||
if (spaceId === DEFAULT_SPACE_ID) { | ||
return undefined; | ||
} | ||
|
||
return spaceId; | ||
/** | ||
* Converts a Space ID string to its namespace ID representation. Note that a Space ID string is equivalent to a namespace string. | ||
* | ||
* See also: {@link namespaceStringToId}. | ||
*/ | ||
export function spaceIdToNamespace(spaceId: string) { | ||
return namespaceStringToId(spaceId); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a perfect world, the concept of a "namespace" would have never leaked out of the saved objects system. It was always meant to be an implementation detail of the Spaces feature, so my vote is to keep "Space ID".
I also agree with your decision to keep these functions around, even though they are functionally equivilent today.
This partially reverts commit fa6ae1c.
Now, options.namespace can use `'default'` interchangeably with `undefined`.
@legrego this PR is ready for another pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to do some manual testing yet, but the only other change I think we need to make is to the encrypted_saved_objects_client_wrapper
(sorry I missed this the first time around). Single-namespace ESOs rely on the object's namespace
as part of the AAD, and it's currently setup to assume that options?.namespace
is the authoritative source of this information. We'll need to tweak this to ensure that we're reading the correct namespace value, like you've already done within the repository:
Lines 157 to 161 in 6627d7d
const namespace = getDescriptorNamespace( | |
this.options.baseTypeRegistry, | |
type, | |
options?.namespace | |
); |
x-pack/test/saved_object_api_integration/security_only/apis/bulk_update.ts
Show resolved
Hide resolved
x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_update.ts
Outdated
Show resolved
Hide resolved
Hm, so we can't depend on the Spaces plugin to translate
I guess we can just duplicate the behavior, agreed? |
ugh. right. I wish I had considered this during my initial review, it could have avoided a lot of churn on your part -- I'm sorry about that. So now that this is in at least three places, I guess I'm less opposed to exporting these namespace functions from core, so long as @elastic/kibana-platform is also ok with that. This is simple enough where duplicating wouldn't be the end of the world either. tl;dr I'm not currently favoring one approach over the other, so I'm happy to defer to you |
No biggie, the partial revert didn't take long!
Sounds good. I don't have a strong opinion to be honest, though duplication does feel a bit off to me. Since Platform is the ultimate owner of this, I'll defer to them. |
I don't have any problems with it. I would prefer we don't just export a bare |
…contract" This reverts commit 0b2f62c.
Done in 2c36a31. |
@elasticmachine merge upstream |
@elasticmachine merge upstream |
// test again with object namespace string that supersedes the operation's namespace ID | ||
await bulkUpdateSuccess([{ ..._obj1, namespace }]); | ||
expectClientCallArgsAction([_obj1], { method: 'update', getId }); | ||
client.bulk.mockClear(); | ||
await bulkUpdateSuccess([{ ..._obj2, namespace }]); | ||
expectClientCallArgsAction([_obj2], { method: 'update', getId, overrides }, 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me that this test actually asserts that the per-object namespace was actually used. I think it would be much more clear if some of these parameters & expectations were string literals rather than stored in a reused variable. IMO that makes it much more clear that this is actually working.
That said, I think most of the tests in this giant file have this issue of being incredibly hard to read due to lots of variable use and lookup needed to actually understand what the test is doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, and you're not the first person to mention that.
When I rewrote these unit tests earlier this year, they didn't have even close to complete coverage, because so many people had changed and tweaked the repository over the years. I mainly wanted to make sure we had more complete coverage. The only reason I decided to abstract out a lot of the test logic into helper functions and variables in lieu of testing with clearer literals is because I didn't want the test file to balloon to 10k+ lines of code 😛 IMO that's even harder to read and maintain.
In the meantime, I added some comments around the test cases that leverage the getId
function argument to clarify it a bit more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for this Joe!
x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts
Show resolved
Hide resolved
.../encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts
Outdated
Show resolved
Hide resolved
There was a typo, and I took the opportunity to reword it a bit too
💚 Build SucceededBuild metricsdistributable file count
History
To update your PR or re-run it, just comment with: |
…s-for-710 * 'master' of github.com:elastic/kibana: (65 commits) Separate url forwarding logic and legacy services (elastic#76892) Bump yargs-parser to v13.1.2+ (elastic#77009) [Ingest Manager] Shared Fleet agent policy action (elastic#76013) [Search] Re-add support for aborting when a connection is closed (elastic#76470) [Search] Remove long-running query pop-up (elastic#75385) [Monitoring] Fix UI error when alerting is not available (elastic#77179) do not log plugin id format warning in dist mode (elastic#77134) [ML] Improving client side error handling (elastic#76743) [Alerting][Connectors] Refactor IBM Resilient: Generic Implementation (phase one) (elastic#74357) [Docs] some basic searchsource api docs (elastic#77038) add cGroupOverrides to the legacy config (elastic#77180) Change saved object bulkUpdate to work across multiple namespaces (elastic#75478) [Security Solution][Resolver] Replace Selectable popover with badges (elastic#76997) Removing ml-state index from archive (elastic#77143) [Security Solution] Add unit tests for histograms (elastic#77081) [Lens] Filters aggregation (elastic#75635) [Enterprise Search] Update WS Overview logic to use new config data (elastic#77122) Cleanup type output before building new types (elastic#77211) [Security Solution] Use safe type in resolver backend (elastic#76969) Use proper lodash syntax (elastic#77105) ... # Conflicts: # x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx
Overview
Now, using bulk update you can optionally specify a namespace string for each object. If an "object namespace" is not defined, the repository will fall back to the operation-wide namespace ID (which is defined by
options.namespace
).This PR is best reviewed one commit at a time:
namespaces
array for each saved object (which is actually array of namespace strings); because of this, OSS should really provide a way to convert namespace strings <-> namespace IDs. This commit makes that change.bulkUpdate
in the saved objects repository/client.Implementation
Two changes were made to bulkUpdate:
Example: update "foo" in the default space, and "bar" in the outer space:
One way:
This would yield the same result:
This would also yield the same result: