-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
SavedObjectsRepository code cleanup #157154
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
66c2db2
start extracting all the things
pgayvallet 1baed7a
extract 'create'
pgayvallet 3104023
extract bulk_create
pgayvallet e9c8d57
extract delete
pgayvallet 2e1add9
extract checkConflicts
pgayvallet bd51cd3
extracting bulkDelete
pgayvallet e268959
extract delete by namespace
pgayvallet 7b5a8b8
extract find
pgayvallet 2dc4634
extract bulk_get
pgayvallet a2f90e8
extract get
pgayvallet 1ed1f1c
extract update
pgayvallet 8b550de
extract bulk_update
pgayvallet cb47abb
extract remove_references_to
pgayvallet d3b338f
extract open point in time
pgayvallet aeed912
extract increment_counter
pgayvallet 109017d
implement resolve and bulk resolve
pgayvallet e7f1e34
move internals
pgayvallet aef0cfa
delete dead code
pgayvallet 91cfdc0
remove obsolete snapshot
pgayvallet 81ea647
some more cleanup
pgayvallet ccfe1c9
fix import path
pgayvallet 65b4bfa
fix import path
pgayvallet b6007d8
fix binding
pgayvallet ba8563a
this was a tricky one
pgayvallet f640226
implement remaining missing methods
pgayvallet b63a1b9
Merge remote-tracking branch 'upstream/main' into kbn-xxx-SOR-cleanup
pgayvallet 85a9c00
small repository cleanup
pgayvallet c83830d
move ALL the things again
pgayvallet eec0fc1
fix constructor error
pgayvallet 2464efd
add mocks and test example
pgayvallet 7b8e966
lint
pgayvallet 48a1be0
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine 5eac343
Merge remote-tracking branch 'upstream/main' into kbn-xxx-SOR-cleanup
pgayvallet 37ccab3
merge util files
pgayvallet 32f9866
add left and right helpers
pgayvallet 04e5ac4
Merge remote-tracking branch 'upstream/main' into kbn-xxx-SOR-cleanup
pgayvallet 69bab51
add description to the package's readme
pgayvallet File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
28 changes: 28 additions & 0 deletions
28
packages/core/saved-objects/core-saved-objects-api-server-internal/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,31 @@ | ||
# @kbn/core-saved-objects-api-server-internal | ||
|
||
This package contains the internal implementation of core's server-side savedObjects client and repository. | ||
|
||
## Structure of the package | ||
|
||
``` | ||
@kbn/core-saved-objects-api-server-internal | ||
- /src/lib | ||
- repository.ts | ||
- /apis | ||
- create.ts | ||
- delete.ts | ||
- .... | ||
- /helpers | ||
- /utils | ||
- /internals | ||
``` | ||
|
||
### lib/apis/utils | ||
Base utility functions, receiving (mostly) parameters from a given API call's option | ||
(e.g the type or id of a document, but not the type registry). | ||
|
||
### lib/apis/helpers | ||
'Stateful' helpers. These helpers were mostly here to receive the utility functions that were extracted from the SOR. | ||
They are instantiated with the SOR's context (e.g type registry, mappings and so on), to avoid the caller to such | ||
helpers to have to pass all the parameters again. | ||
|
||
### lib/apis/internals | ||
I would call them 'utilities with business logic'. These are the 'big' chunks of logic called by the APIs. | ||
E.g preflightCheckForCreate, internalBulkResolve and so on. |
3 changes: 0 additions & 3 deletions
3
...-saved-objects-api-server-internal/src/lib/__snapshots__/priority_collection.test.ts.snap
This file was deleted.
Oops, something went wrong.
313 changes: 313 additions & 0 deletions
313
...ges/core/saved-objects/core-saved-objects-api-server-internal/src/lib/apis/bulk_create.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import type { Payload } from '@hapi/boom'; | ||
import { | ||
SavedObjectsErrorHelpers, | ||
type SavedObject, | ||
type SavedObjectSanitizedDoc, | ||
DecoratedError, | ||
AuthorizeCreateObject, | ||
SavedObjectsRawDoc, | ||
} from '@kbn/core-saved-objects-server'; | ||
import { SavedObjectsUtils } from '@kbn/core-saved-objects-utils-server'; | ||
import { | ||
SavedObjectsCreateOptions, | ||
SavedObjectsBulkCreateObject, | ||
SavedObjectsBulkResponse, | ||
} from '@kbn/core-saved-objects-api-server'; | ||
import { DEFAULT_REFRESH_SETTING } from '../constants'; | ||
import { | ||
Either, | ||
getBulkOperationError, | ||
getCurrentTime, | ||
getExpectedVersionProperties, | ||
left, | ||
right, | ||
isLeft, | ||
isRight, | ||
normalizeNamespace, | ||
setManaged, | ||
errorContent, | ||
} from './utils'; | ||
import { getSavedObjectNamespaces } from './utils'; | ||
import { PreflightCheckForCreateObject } from './internals/preflight_check_for_create'; | ||
import { ApiExecutionContext } from './types'; | ||
|
||
export interface PerformBulkCreateParams<T = unknown> { | ||
objects: Array<SavedObjectsBulkCreateObject<T>>; | ||
options: SavedObjectsCreateOptions; | ||
} | ||
|
||
type ExpectedResult = Either< | ||
{ type: string; id?: string; error: Payload }, | ||
{ | ||
method: 'index' | 'create'; | ||
object: SavedObjectsBulkCreateObject & { id: string }; | ||
preflightCheckIndex?: number; | ||
} | ||
>; | ||
|
||
export const performBulkCreate = async <T>( | ||
{ objects, options }: PerformBulkCreateParams<T>, | ||
{ | ||
registry, | ||
helpers, | ||
allowedTypes, | ||
client, | ||
serializer, | ||
migrator, | ||
extensions = {}, | ||
}: ApiExecutionContext | ||
): Promise<SavedObjectsBulkResponse<T>> => { | ||
const { | ||
common: commonHelper, | ||
validation: validationHelper, | ||
encryption: encryptionHelper, | ||
preflight: preflightHelper, | ||
serializer: serializerHelper, | ||
} = helpers; | ||
const { securityExtension } = extensions; | ||
const namespace = commonHelper.getCurrentNamespace(options.namespace); | ||
|
||
const { | ||
migrationVersionCompatibility, | ||
overwrite = false, | ||
refresh = DEFAULT_REFRESH_SETTING, | ||
managed: optionsManaged, | ||
} = options; | ||
const time = getCurrentTime(); | ||
|
||
let preflightCheckIndexCounter = 0; | ||
const expectedResults = objects.map<ExpectedResult>((object) => { | ||
const { type, id: requestId, initialNamespaces, version, managed } = object; | ||
let error: DecoratedError | undefined; | ||
let id: string = ''; // Assign to make TS happy, the ID will be validated (or randomly generated if needed) during getValidId below | ||
const objectManaged = managed; | ||
if (!allowedTypes.includes(type)) { | ||
error = SavedObjectsErrorHelpers.createUnsupportedTypeError(type); | ||
} else { | ||
try { | ||
id = commonHelper.getValidId(type, requestId, version, overwrite); | ||
validationHelper.validateInitialNamespaces(type, initialNamespaces); | ||
validationHelper.validateOriginId(type, object); | ||
} catch (e) { | ||
error = e; | ||
} | ||
} | ||
|
||
if (error) { | ||
return left({ id: requestId, type, error: errorContent(error) }); | ||
} | ||
|
||
const method = requestId && overwrite ? 'index' : 'create'; | ||
const requiresNamespacesCheck = requestId && registry.isMultiNamespace(type); | ||
|
||
return right({ | ||
method, | ||
object: { | ||
...object, | ||
id, | ||
managed: setManaged({ optionsManaged, objectManaged }), | ||
}, | ||
...(requiresNamespacesCheck && { preflightCheckIndex: preflightCheckIndexCounter++ }), | ||
}) as ExpectedResult; | ||
}); | ||
|
||
const validObjects = expectedResults.filter(isRight); | ||
if (validObjects.length === 0) { | ||
// We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. | ||
return { | ||
// Technically the returned array should only contain SavedObject results, but for errors this is not true (we cast to 'unknown' below) | ||
saved_objects: expectedResults.map<SavedObject<T>>( | ||
({ value }) => value as unknown as SavedObject<T> | ||
), | ||
}; | ||
} | ||
|
||
const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); | ||
const preflightCheckObjects = validObjects | ||
.filter(({ value }) => value.preflightCheckIndex !== undefined) | ||
.map<PreflightCheckForCreateObject>(({ value }) => { | ||
const { type, id, initialNamespaces } = value.object; | ||
const namespaces = initialNamespaces ?? [namespaceString]; | ||
return { type, id, overwrite, namespaces }; | ||
}); | ||
const preflightCheckResponse = await preflightHelper.preflightCheckForCreate( | ||
preflightCheckObjects | ||
); | ||
|
||
const authObjects: AuthorizeCreateObject[] = validObjects.map((element) => { | ||
const { object, preflightCheckIndex: index } = element.value; | ||
const preflightResult = index !== undefined ? preflightCheckResponse[index] : undefined; | ||
return { | ||
type: object.type, | ||
id: object.id, | ||
initialNamespaces: object.initialNamespaces, | ||
existingNamespaces: preflightResult?.existingDocument?._source.namespaces ?? [], | ||
}; | ||
}); | ||
|
||
const authorizationResult = await securityExtension?.authorizeBulkCreate({ | ||
namespace, | ||
objects: authObjects, | ||
}); | ||
|
||
let bulkRequestIndexCounter = 0; | ||
const bulkCreateParams: object[] = []; | ||
type ExpectedBulkResult = Either< | ||
{ type: string; id?: string; error: Payload }, | ||
{ esRequestIndex: number; requestedId: string; rawMigratedDoc: SavedObjectsRawDoc } | ||
>; | ||
const expectedBulkResults = await Promise.all( | ||
expectedResults.map<Promise<ExpectedBulkResult>>(async (expectedBulkGetResult) => { | ||
if (isLeft(expectedBulkGetResult)) { | ||
return expectedBulkGetResult; | ||
} | ||
|
||
let savedObjectNamespace: string | undefined; | ||
let savedObjectNamespaces: string[] | undefined; | ||
let existingOriginId: string | undefined; | ||
let versionProperties; | ||
const { | ||
preflightCheckIndex, | ||
object: { initialNamespaces, version, ...object }, | ||
method, | ||
} = expectedBulkGetResult.value; | ||
if (preflightCheckIndex !== undefined) { | ||
const preflightResult = preflightCheckResponse[preflightCheckIndex]; | ||
const { type, id, existingDocument, error } = preflightResult; | ||
if (error) { | ||
const { metadata } = error; | ||
return left({ | ||
id, | ||
type, | ||
error: { | ||
...errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)), | ||
...(metadata && { metadata }), | ||
}, | ||
}); | ||
} | ||
savedObjectNamespaces = | ||
initialNamespaces || getSavedObjectNamespaces(namespace, existingDocument); | ||
versionProperties = getExpectedVersionProperties(version); | ||
existingOriginId = existingDocument?._source?.originId; | ||
} else { | ||
if (registry.isSingleNamespace(object.type)) { | ||
savedObjectNamespace = initialNamespaces | ||
? normalizeNamespace(initialNamespaces[0]) | ||
: namespace; | ||
} else if (registry.isMultiNamespace(object.type)) { | ||
savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace); | ||
} | ||
versionProperties = getExpectedVersionProperties(version); | ||
} | ||
|
||
// 1. If the originId has been *explicitly set* in the options (defined or undefined), respect that. | ||
// 2. Otherwise, preserve the originId of the existing object that is being overwritten, if any. | ||
const originId = Object.keys(object).includes('originId') | ||
? object.originId | ||
: existingOriginId; | ||
const migrated = migrator.migrateDocument({ | ||
id: object.id, | ||
type: object.type, | ||
attributes: await encryptionHelper.optionallyEncryptAttributes( | ||
object.type, | ||
object.id, | ||
savedObjectNamespace, // only used for multi-namespace object types | ||
object.attributes | ||
), | ||
migrationVersion: object.migrationVersion, | ||
coreMigrationVersion: object.coreMigrationVersion, | ||
typeMigrationVersion: object.typeMigrationVersion, | ||
...(savedObjectNamespace && { namespace: savedObjectNamespace }), | ||
...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), | ||
managed: setManaged({ optionsManaged, objectManaged: object.managed }), | ||
updated_at: time, | ||
created_at: time, | ||
references: object.references || [], | ||
originId, | ||
}) as SavedObjectSanitizedDoc<T>; | ||
|
||
/** | ||
* If a validation has been registered for this type, we run it against the migrated attributes. | ||
* This is an imperfect solution because malformed attributes could have already caused the | ||
* migration to fail, but it's the best we can do without devising a way to run validations | ||
* inside the migration algorithm itself. | ||
*/ | ||
try { | ||
validationHelper.validateObjectForCreate(object.type, migrated); | ||
} catch (error) { | ||
return left({ | ||
id: object.id, | ||
type: object.type, | ||
error, | ||
}); | ||
} | ||
|
||
const expectedResult = { | ||
esRequestIndex: bulkRequestIndexCounter++, | ||
requestedId: object.id, | ||
rawMigratedDoc: serializer.savedObjectToRaw(migrated), | ||
}; | ||
|
||
bulkCreateParams.push( | ||
{ | ||
[method]: { | ||
_id: expectedResult.rawMigratedDoc._id, | ||
_index: commonHelper.getIndexForType(object.type), | ||
...(overwrite && versionProperties), | ||
}, | ||
}, | ||
expectedResult.rawMigratedDoc._source | ||
); | ||
|
||
return right(expectedResult); | ||
}) | ||
); | ||
|
||
const bulkResponse = bulkCreateParams.length | ||
? await client.bulk({ | ||
refresh, | ||
require_alias: true, | ||
body: bulkCreateParams, | ||
}) | ||
: undefined; | ||
|
||
const result = { | ||
saved_objects: expectedBulkResults.map((expectedResult) => { | ||
if (isLeft(expectedResult)) { | ||
return expectedResult.value as any; | ||
} | ||
|
||
const { requestedId, rawMigratedDoc, esRequestIndex } = expectedResult.value; | ||
const rawResponse = Object.values(bulkResponse?.items[esRequestIndex] ?? {})[0] as any; | ||
|
||
const error = getBulkOperationError(rawMigratedDoc._source.type, requestedId, rawResponse); | ||
if (error) { | ||
return { type: rawMigratedDoc._source.type, id: requestedId, error }; | ||
} | ||
|
||
// When method == 'index' the bulkResponse doesn't include the indexed | ||
// _source so we return rawMigratedDoc but have to spread the latest | ||
// _seq_no and _primary_term values from the rawResponse. | ||
return serializerHelper.rawToSavedObject( | ||
{ | ||
...rawMigratedDoc, | ||
...{ _seq_no: rawResponse._seq_no, _primary_term: rawResponse._primary_term }, | ||
}, | ||
{ migrationVersionCompatibility } | ||
); | ||
}), | ||
}; | ||
return encryptionHelper.optionallyDecryptAndRedactBulkResult( | ||
result, | ||
authorizationResult?.typeMap, | ||
objects | ||
); | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 function still seems like such a beast 😄
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.
Yeah. I was planning on using the remaining time this week trying to do some quick wins simplifying some APIs