Delete legacy URL aliases when objects are deleted or unshared#117056
Conversation
Also needed to update the legacy-url-alias type mapping to make the query work.
bd79e83 to
1be71e8
Compare
1be71e8 to
4236bb9
Compare
|
Pinging @elastic/kibana-security (Team:Security) |
pgayvallet
left a comment
There was a problem hiding this comment.
LGTM. A few minor comments
| // Legacy URL aliases cannot exist in the default space; filter that out | ||
| const filteredNamespaces = namespaces.filter( | ||
| (namespace) => namespace !== DEFAULT_NAMESPACE_STRING | ||
| ); |
There was a problem hiding this comment.
I wasn't even aware of that tbh...
There was a problem hiding this comment.
Yeah, aliases are only created when objects in non-default spaces are converted, and I also designed resolve with that assumption in mind. Technically we could have chosen to support aliases in the default space (with a default: prefix in the raw ID) but there wasn't any practical reason to do so.
| deleteLegacyUrlAliases({ | ||
| mappings, | ||
| registry, | ||
| client, | ||
| getIndexForType, | ||
| type, | ||
| id, | ||
| namespaces, | ||
| deleteBehavior, |
There was a problem hiding this comment.
That's a shame we can't bulkDeleteLegacyUrlAliases, but given that namespaces and deleteBehavior can be different per-object, I'm not even sure this would be doable with a single query/script.
There was a problem hiding this comment.
Yeah, I thought about it for a while and I just don't see a way to do this with a single query, unless we passed in tons of painless script parameters (a map that has different values for each object type:id), and I'm not sure how that would perform.
Since deleting and unsharing objects should happen infrequently, I think it's OK to make individual calls to deleteLegacyUrlAliases, and a simple query and script is much safer and more reliable than a big complex one.
|
|
||
| // The test fixture contains three legacy URL aliases: | ||
| // (1) one for "space_1", (2) one for "space_2", and (3) one for "other_space", which is a non-existent space. | ||
| // The test fixture contains six legacy URL aliases: |
|
|
||
| if (expectAliasDifference !== undefined) { | ||
| // if we deleted an object that had an alias pointing to it, the alias should have been deleted as well | ||
| await es.indices.refresh({ index: '.kibana' }); // alias deletion uses refresh: false, so we need to manually refresh the index before searching |
There was a problem hiding this comment.
NIT/optional: I think we could refresh only once if objects.find((obj) => obj.expectAliasDifference !== undefined)
src/core/server/saved_objects/service/lib/legacy_url_aliases/delete_legacy_url_aliases.ts
Outdated
Show resolved
Hide resolved
| { ignore: [404] } | ||
| ); | ||
| } catch (err) { | ||
| const { error } = err.body; |
There was a problem hiding this comment.
We're handling expected ES errors here, but an unexpected error may not have a body property, or that body property may not have an error property. If we encounter an error during this error handling, then we'll lose the origial error context and have a difficult time tracking down exactly what happened
There was a problem hiding this comment.
Good call, I found this function that the core ES client module provides:
kibana/src/core/server/elasticsearch/client/configure_client.ts
Lines 83 to 93 in 2cd007e
I'll reuse it here
| }).catch((err) => { | ||
| // The object has already been unshared, but we caught an error when attempting to delete aliases. | ||
| // A consumer cannot attempt to unshare the object again, so just log the error and swallow it. | ||
| logger.error(err.message); |
There was a problem hiding this comment.
Should this error have more context?
| logger.error(err.message); | |
| logger.error(`Failed to delete legacy URL aliases: ${err.message}`); |
There was a problem hiding this comment.
The error originates from deleteLegacyUrlAliases, and those error messages are pretty detailed.
Are you suggesting we should change this message to mention that this is happening during updateObjectsSpaces?
There was a problem hiding this comment.
The error originates from
deleteLegacyUrlAliases, and those error messages are pretty detailed. Are you suggesting we should change this message to mention that this is happening duringupdateObjectsSpaces?
I agree that the expected error messages are pretty detailed, and there is currently little room for unexpected errors to occur the way that deleteLegacyUrlAliases is constructed. Yeah I think mentioning that this is happening during updateObjectsSpaces would be sufficient. It gives us another piece of "grepable" text in the error message should we ever have to trace this in production
There was a problem hiding this comment.
Great, done for both consumers in ed54e64
...test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json
Show resolved
Hide resolved
💚 Build Succeeded
Metrics [docs]Saved Objects .kibana field count
History
To update your PR or re-run it, just comment with: |
💚 Backport successful
This backport PR will be merged automatically after passing CI. |
…) (#117461) Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>

Resolves #116235.
Overview
Whenever a Saved Objects Client operation causes an existing legacy URL alias to no longer having a valid target, the alias should be deleted.
There are three situations where a saved object can be removed from a space in Kibana:
This PR aims to address (2) and (3) above.
Implementation
I introduced a new
deleteLegacyUrlAliasesmodule in Core. This has to be able to delete an alias given an object type, ID, and the space(s) that the object has been removed from. However, if an object is shared, it may have previously existed in numerous spaces and/or "*" (all current and future spaces).Therefore the alias deletion cannot be accomplished with a simple delete or even a bulk operation, we need to query for any "inbound aliases" for a given object in a given set of spaces. The implementation uses the Elasticsearch
updateByQueryAPI under the hood, which supports three operations:update,delete, andnoop. We only need to usedeleteandnoop.There are two consumers of this new module:
SavedObjectsRepository.deleteupdateObjectsSpacesThis module is optimized in a few ways:
updateObjectsSpacesmodule, we may be potentially updating spaces for dozens or hundreds of different saved objects at the same time, but we potentially have to calldeleteLegacyUrlAliasesfor each one; as such, we usep-mapto limit the concurrency ofdeleteLegacyUrlAliasescalls to 10 at a time (we've done this before in theimportAPI)Testing
Functional tests have been added to test both "inclusive" and "exclusive" alias deletion in both consumers listed above.
Manual testing for this case is not too straightforward, but you could accomplish it by 1. generating staging data for objects and aliases, 2. adding it to Kibana, 3. calling the HTTP APIs for
deleteorupdateObjectsSpacesfor some of the objects, then 4. use Dev Tools to check and confirm the aliases were deleted as expected.