Skip to content

Commit

Permalink
[Backport 2.x][Workspace]Optional workspaces params in repository (#5949
Browse files Browse the repository at this point in the history
) (#6387)

* [Workspace]Optional workspaces params in repository (#5949)

* refact: move workspace specific logic to savedObjectWrapper

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: some error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: fix test error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: remove useless config in test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add CHANGELOG

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add more unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: revert test in repository.test.js

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: revert test in import_saved_objects.test.ts

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: revert test in repository.test.js

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add type

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: bootstrap type error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize code and add comment

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: integration test fail

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add missing code

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize code

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* Add permissions field to the mapping only if the permission control is enabled

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Fix test failure

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* feat: modify unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: bulk create error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: bulk create error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add new config in yml file

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add new config in yml file

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: update yml file

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: fix unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: do not skip migration when doing integration test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: remove useless code

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: remove useless code

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: change flag variable

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add test cases

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
Signed-off-by: gaobinlong <gbinlong@amazon.com>
Co-authored-by: gaobinlong <gbinlong@amazon.com>
(cherry picked from commit c6b4c34)

* [Workspace]Following pr for #5949 (#6012)

* feat: temp save

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add some comment

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
Co-authored-by: Ashwin P Chandran <ashwinpc@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
Co-authored-by: Ashwin P Chandran <ashwinpc@amazon.com>
  • Loading branch information
SuZhou-Joe and ashwin-pc authored Apr 11, 2024
1 parent 52b734a commit 38742b1
Show file tree
Hide file tree
Showing 36 changed files with 1,402 additions and 15 deletions.
3 changes: 3 additions & 0 deletions config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,6 @@

# Set the value of this setting to true to enable plugin augmentation
# vis_augmenter.pluginAugmentationEnabled: true

# Set the value to true to enable workspace feature
# workspace.enabled: false
1 change: 1 addition & 0 deletions src/core/public/saved_objects/saved_objects_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ export class SavedObjectsClient {
filter: 'filter',
namespaces: 'namespaces',
preference: 'preference',
workspaces: 'workspaces',
};

const renamedQuery = renameKeys<SavedObjectsFindOptions, any>(renameMap, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export interface SavedObjectsExportOptions {
excludeExportDetails?: boolean;
/** optional namespace to override the namespace used by the savedObjectsClient. */
namespace?: string;
/** optional workspaces to override the workspaces used by the savedObjectsClient. */
workspaces?: string[];
}

/**
Expand Down Expand Up @@ -87,13 +89,15 @@ async function fetchObjectsToExport({
exportSizeLimit,
savedObjectsClient,
namespace,
workspaces,
}: {
objects?: SavedObjectsExportOptions['objects'];
types?: string[];
search?: string;
exportSizeLimit: number;
savedObjectsClient: SavedObjectsClientContract;
namespace?: string;
workspaces?: string[];
}) {
if ((types?.length ?? 0) > 0 && (objects?.length ?? 0) > 0) {
throw Boom.badRequest(`Can't specify both "types" and "objects" properties when exporting`);
Expand Down Expand Up @@ -121,6 +125,7 @@ async function fetchObjectsToExport({
search,
perPage: exportSizeLimit,
namespaces: namespace ? [namespace] : undefined,
...(workspaces ? { workspaces } : {}),
});
if (findResponse.total > exportSizeLimit) {
throw Boom.badRequest(`Can't export more than ${exportSizeLimit} objects`);
Expand Down Expand Up @@ -153,6 +158,7 @@ export async function exportSavedObjectsToStream({
includeReferencesDeep = false,
excludeExportDetails = false,
namespace,
workspaces,
}: SavedObjectsExportOptions) {
const rootObjects = await fetchObjectsToExport({
types,
Expand All @@ -161,6 +167,7 @@ export async function exportSavedObjectsToStream({
savedObjectsClient,
exportSizeLimit,
namespace,
workspaces,
});
let exportedObjects: Array<SavedObject<unknown>> = [];
let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = [];
Expand Down
3 changes: 3 additions & 0 deletions src/core/server/saved_objects/import/check_conflicts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface CheckConflictsParams {
ignoreRegularConflicts?: boolean;
retries?: SavedObjectsImportRetry[];
createNewCopies?: boolean;
workspaces?: string[];
}

const isUnresolvableConflict = (error: SavedObjectError) =>
Expand All @@ -56,6 +57,7 @@ export async function checkConflicts({
ignoreRegularConflicts,
retries = [],
createNewCopies,
workspaces,
}: CheckConflictsParams) {
const filteredObjects: Array<SavedObject<{ title?: string }>> = [];
const errors: SavedObjectsImportError[] = [];
Expand All @@ -77,6 +79,7 @@ export async function checkConflicts({
});
const checkConflictsResult = await savedObjectsClient.checkConflicts(objectsToCheck, {
namespace,
workspaces,
});
const errorMap = checkConflictsResult.errors.reduce(
(acc, { type, id, error }) => acc.set(`${type}:${id}`, error),
Expand Down
3 changes: 3 additions & 0 deletions src/core/server/saved_objects/import/create_saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface CreateSavedObjectsParams<T> {
overwrite?: boolean;
dataSourceId?: string;
dataSourceTitle?: string;
workspaces?: string[];
}
interface CreateSavedObjectsResult<T> {
createdObjects: Array<CreatedObject<T>>;
Expand All @@ -61,6 +62,7 @@ export const createSavedObjects = async <T>({
overwrite,
dataSourceId,
dataSourceTitle,
workspaces,
}: CreateSavedObjectsParams<T>): Promise<CreateSavedObjectsResult<T>> => {
// filter out any objects that resulted in errors
const errorSet = accumulatedErrors.reduce(
Expand Down Expand Up @@ -197,6 +199,7 @@ export const createSavedObjects = async <T>({
const bulkCreateResponse = await savedObjectsClient.bulkCreate(objectsToCreate, {
namespace,
overwrite,
workspaces,
});
expectedResults = bulkCreateResponse.saved_objects;
}
Expand Down
3 changes: 3 additions & 0 deletions src/core/server/saved_objects/import/import_saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export async function importSavedObjectsFromStream({
namespace,
dataSourceId,
dataSourceTitle,
workspaces,
}: SavedObjectsImportOptions): Promise<SavedObjectsImportResponse> {
let errorAccumulator: SavedObjectsImportError[] = [];
const supportedTypes = typeRegistry.getImportableAndExportableTypes().map((type) => type.name);
Expand Down Expand Up @@ -92,6 +93,7 @@ export async function importSavedObjectsFromStream({
savedObjectsClient,
namespace,
ignoreRegularConflicts: overwrite,
workspaces,
};

const checkConflictsResult = await checkConflicts(checkConflictsParams);
Expand Down Expand Up @@ -143,6 +145,7 @@ export async function importSavedObjectsFromStream({
namespace,
dataSourceId,
dataSourceTitle,
...(workspaces ? { workspaces } : {}),
};
const createSavedObjectsResult = await createSavedObjects(createSavedObjectsParams);
errorAccumulator = [...errorAccumulator, ...createSavedObjectsResult.errors];
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/saved_objects/import/resolve_import_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export async function resolveSavedObjectsImportErrors({
createNewCopies,
dataSourceId,
dataSourceTitle,
workspaces,
}: SavedObjectsResolveImportErrorsOptions): Promise<SavedObjectsImportResponse> {
// throw a BadRequest error if we see invalid retries
validateRetries(retries);
Expand Down Expand Up @@ -163,6 +164,7 @@ export async function resolveSavedObjectsImportErrors({
overwrite,
dataSourceId,
dataSourceTitle,
workspaces,
};
const { createdObjects, errors: bulkCreateErrors } = await createSavedObjects(
createSavedObjectsParams
Expand Down
4 changes: 4 additions & 0 deletions src/core/server/saved_objects/import/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ export interface SavedObjectsImportOptions {
createNewCopies: boolean;
dataSourceId?: string;
dataSourceTitle?: string;
/** if specified, will import in given workspaces */
workspaces?: string[];
}

/**
Expand All @@ -212,6 +214,8 @@ export interface SavedObjectsResolveImportErrorsOptions {
createNewCopies: boolean;
dataSourceId?: string;
dataSourceTitle?: string;
/** if specified, will import in given workspaces */
workspaces?: string[];
}

export type CreatedObject<T> = SavedObject<T> & { destinationId?: string };
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import { IndexMapping, SavedObjectsTypeMappingDefinitions } from './../../mappings';
import { buildActiveMappings, diffMappings } from './build_active_mappings';
import { configMock } from '../../../config/mocks';

describe('buildActiveMappings', () => {
test('creates a strict mapping', () => {
Expand Down Expand Up @@ -91,6 +92,12 @@ describe('buildActiveMappings', () => {
expect(hashes.aaa).toEqual(hashes.bbb);
expect(hashes.aaa).not.toEqual(hashes.ccc);
});

test('workspaces field is added when workspace feature flag is enabled', () => {
const rawConfig = configMock.create();
rawConfig.get.mockReturnValue(true);
expect(buildActiveMappings({}, rawConfig)).toHaveProperty('properties.workspaces');
});
});

describe('diffMappings', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import crypto from 'crypto';
import { cloneDeep, mapValues } from 'lodash';
import { Config } from '@osd/config';
import {
IndexMapping,
SavedObjectsMappingProperties,
Expand All @@ -47,11 +48,20 @@ import {
* @param typeDefinitions - the type definitions to build mapping from.
*/
export function buildActiveMappings(
typeDefinitions: SavedObjectsTypeMappingDefinitions | SavedObjectsMappingProperties
typeDefinitions: SavedObjectsTypeMappingDefinitions | SavedObjectsMappingProperties,
opensearchDashboardsRawConfig?: Config
): IndexMapping {
const mapping = defaultMapping();

const mergedProperties = validateAndMerge(mapping.properties, typeDefinitions);
let mergedProperties = validateAndMerge(mapping.properties, typeDefinitions);
// if permission control for saved objects is enabled, the permissions field should be added to the mapping
if (opensearchDashboardsRawConfig?.get('workspace.enabled')) {
mergedProperties = validateAndMerge(mapping.properties, {
workspaces: {
type: 'keyword',
},
});
}

return cloneDeep({
...mapping,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { IndexMigrator } from './index_migrator';
import { MigrationOpts } from './migration_context';
import { loggingSystemMock } from '../../../logging/logging_system.mock';
import { configMock } from '../../../config/mocks';

describe('IndexMigrator', () => {
let testOpts: jest.Mocked<MigrationOpts> & {
Expand All @@ -59,6 +60,60 @@ describe('IndexMigrator', () => {
};
});

test('creates the index when workspaces feature flag is enabled', async () => {
const { client } = testOpts;

testOpts.mappingProperties = { foo: { type: 'long' } as any };
const rawConfig = configMock.create();
rawConfig.get.mockReturnValue(true);
testOpts.opensearchDashboardsRawConfig = rawConfig;

withIndex(client, { index: { statusCode: 404 }, alias: { statusCode: 404 } });

await new IndexMigrator(testOpts).migrate();

expect(client.indices.create).toHaveBeenCalledWith({
body: {
mappings: {
dynamic: 'strict',
_meta: {
migrationMappingPropertyHashes: {
foo: '18c78c995965207ed3f6e7fc5c6e55fe',
migrationVersion: '4a1746014a75ade3a714e1db5763276f',
namespace: '2f4316de49999235636386fe51dc06c1',
namespaces: '2f4316de49999235636386fe51dc06c1',
originId: '2f4316de49999235636386fe51dc06c1',
references: '7997cf5a56cc02bdc9c93361bde732b0',
type: '2f4316de49999235636386fe51dc06c1',
updated_at: '00da57df13e94e9d98437d13ace4bfe0',
workspaces: '2f4316de49999235636386fe51dc06c1',
},
},
properties: {
foo: { type: 'long' },
migrationVersion: { dynamic: 'true', type: 'object' },
namespace: { type: 'keyword' },
namespaces: { type: 'keyword' },
originId: { type: 'keyword' },
type: { type: 'keyword' },
updated_at: { type: 'date' },
references: {
type: 'nested',
properties: {
name: { type: 'keyword' },
type: { type: 'keyword' },
id: { type: 'keyword' },
},
},
workspaces: { type: 'keyword' },
},
},
settings: { number_of_shards: 1, auto_expand_replicas: '0-1' },
},
index: '.kibana_1',
});
});

test('creates the index if it does not exist', async () => {
const { client } = testOpts;

Expand Down
16 changes: 12 additions & 4 deletions src/core/server/saved_objects/migrations/core/migration_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
*/

import { Logger } from 'src/core/server/logging';
import { Config } from '@osd/config';
import { MigrationOpenSearchClient } from './migration_opensearch_client';
import { SavedObjectsSerializer } from '../../serialization';
import {
Expand Down Expand Up @@ -65,6 +66,7 @@ export interface MigrationOpts {
* prior to running migrations. For example: 'opensearch_dashboards_index_template*'
*/
obsoleteIndexTemplatePattern?: string;
opensearchDashboardsRawConfig?: Config;
}

/**
Expand All @@ -90,10 +92,15 @@ export interface Context {
* and various info needed to migrate the source index.
*/
export async function migrationContext(opts: MigrationOpts): Promise<Context> {
const { log, client } = opts;
const { log, client, opensearchDashboardsRawConfig } = opts;
const alias = opts.index;
const source = createSourceContext(await Index.fetchInfo(client, alias), alias);
const dest = createDestContext(source, alias, opts.mappingProperties);
const dest = createDestContext(
source,
alias,
opts.mappingProperties,
opensearchDashboardsRawConfig
);

return {
client,
Expand Down Expand Up @@ -125,10 +132,11 @@ function createSourceContext(source: Index.FullIndexInfo, alias: string) {
function createDestContext(
source: Index.FullIndexInfo,
alias: string,
typeMappingDefinitions: SavedObjectsTypeMappingDefinitions
typeMappingDefinitions: SavedObjectsTypeMappingDefinitions,
opensearchDashboardsRawConfig?: Config
): Index.FullIndexInfo {
const targetMappings = disableUnknownTypeMappingFields(
buildActiveMappings(typeMappingDefinitions),
buildActiveMappings(typeMappingDefinitions, opensearchDashboardsRawConfig),
source.mappings
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
import { loggingSystemMock } from '../../../logging/logging_system.mock';
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { SavedObjectsType } from '../../types';
import { configMock } from '../../../config/mocks';

const createRegistry = (types: Array<Partial<SavedObjectsType>>) => {
const registry = new SavedObjectTypeRegistry();
Expand Down Expand Up @@ -76,6 +77,12 @@ describe('OpenSearchDashboardsMigrator', () => {
const mappings = new OpenSearchDashboardsMigrator(options).getActiveMappings();
expect(mappings).toMatchSnapshot();
});

it('workspaces field exists in the mappings when the feature is enabled', () => {
const options = mockOptions(true);
const mappings = new OpenSearchDashboardsMigrator(options).getActiveMappings();
expect(mappings).toHaveProperty('properties.workspaces');
});
});

describe('runMigrations', () => {
Expand Down Expand Up @@ -146,7 +153,12 @@ type MockedOptions = OpenSearchDashboardsMigratorOptions & {
client: ReturnType<typeof opensearchClientMock.createOpenSearchClient>;
};

const mockOptions = () => {
const mockOptions = (isWorkspaceEnabled?: boolean) => {
const rawConfig = configMock.create();
rawConfig.get.mockReturnValue(false);
if (isWorkspaceEnabled) {
rawConfig.get.mockReturnValue(true);
}
const options: MockedOptions = {
logger: loggingSystemMock.create().get(),
opensearchDashboardsVersion: '8.2.3',
Expand Down Expand Up @@ -186,6 +198,7 @@ const mockOptions = () => {
skip: false,
},
client: opensearchClientMock.createOpenSearchClient(),
opensearchDashboardsRawConfig: rawConfig,
};
return options;
};
Loading

0 comments on commit 38742b1

Please sign in to comment.