Skip to content
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

feat: create compactStructuredEncryptionData helper MONGOSH-1207 #1281

Merged
merged 9 commits into from
May 17, 2022
49 changes: 49 additions & 0 deletions packages/cli-repl/test/e2e-fle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ describe('FLE tests', () => {
expect(plainMongoResult).to.not.include("phoneNumber: '+12874627836445'");
});

it('does not allow compactStructuredEncryptionData command when mongo instance configured without auto encryption', async() => {
const shell = TestShell.start({
args: [await testServer.connectionString()]
});
await shell.waitForPrompt();

const compactResult = await shell.executeLine('db.test.compactStructuredEncryptionData()');
expect(compactResult).to.include('The "compactStructuredEncryptionData" command requires Mongo instance configured with auto encryption.');
});

context('6.0+', () => {
skipIfServerVersion(testServer, '< 6.0'); // FLE2 only available on 6.0+

Expand Down Expand Up @@ -313,6 +323,45 @@ describe('FLE tests', () => {
expect(collections).to.not.include('enxcol_.collfle2.ecoc');
expect(collections).to.not.include('collfle2');
});

it('allows compactStructuredEncryptionData command when mongo instance configured with auto encryption', async() => {
const shell = TestShell.start({
args: ['--nodb', `--csfleLibraryPath=${csfleLibrary}`]
});
const uri = JSON.stringify(await testServer.connectionString());

await shell.waitForPrompt();

await shell.executeLine('local = { key: BinData(0, "kh4Gv2N8qopZQMQYMEtww/AkPsIrXNmEMxTrs3tUoTQZbZu4msdRUaR8U5fXD7A7QXYHcEvuu4WctJLoT+NvvV3eeIg3MD+K8H9SR794m/safgRHdIfy6PD+rFpvmFbY") }');

await shell.executeLine(`keyMongo = Mongo(${uri}, { \
keyVaultNamespace: '${dbname}.keyVault', \
kmsProviders: { local } \
});`);

await shell.executeLine('keyVault = keyMongo.getKeyVault();');
await shell.executeLine('keyId = keyVault.createKey("local");');

await shell.executeLine(`encryptedFieldsMap = { \
'${dbname}.test': { \
fields: [{ path: 'phoneNumber', keyId, bsonType: 'string' }] \
} \
};`);

await shell.executeLine(`autoMongo = Mongo(${uri}, { \
keyVaultNamespace: '${dbname}.keyVault', \
kmsProviders: { local }, \
encryptedFieldsMap \
});`);

await shell.executeLine(`autoMongo.getDB('${dbname}').createCollection('test', { encryptedFields: { fields: [] } });`);
await shell.executeLine(`autoMongo.getDB('${dbname}').test.insertOne({ \
phoneNumber: '+12874627836445' \
});`);

const compactResult = await shell.executeLine(`autoMongo.getDB('${dbname}').test.compactStructuredEncryptionData()`);
expect(compactResult).to.include('ok: 1');
});
});

it('performs KeyVault data key management as expected', async() => {
Expand Down
5 changes: 5 additions & 0 deletions packages/i18n/src/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,11 @@ const translations: Catalog = {
description: 'Updates all documents that match the specified filter for a collection.',
example: 'db.collection.updateMany(filter, update, options)'
},
compactStructuredEncryptionData: {
link: '',
description: 'Compacts structured encryption data',
example: 'db.collection.compactStructuredEncryptionData()'
},
convertToCapped: {
link: '',
description: "calls {convertToCapped:'coll', size:maxBytes}} command",
Expand Down
19 changes: 17 additions & 2 deletions packages/shell-api/src/collection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1939,6 +1939,18 @@ describe('Collection', () => {
);
});
});

describe('compactStructuredEncryptionData', () => {
it('calls service provider runCommandWithCheck', async() => {
const result = await collection.compactStructuredEncryptionData();

expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
'db1',
{ compactStructuredEncryptionData: 'collfle2' }
);
expect(result).to.be.deep.equal({ ok: 1 });
});
});
});
describe('with session', () => {
let serviceProvider: StubbedInstance<ServiceProvider>;
Expand All @@ -1965,6 +1977,7 @@ describe('Collection', () => {
getIndexKeys: { m: 'getIndexes', i: 2 },
dropIndex: { m: 'runCommandWithCheck', i: 2 },
dropIndexes: { m: 'runCommandWithCheck', i: 2 },
compactStructuredEncryptionData: { m: 'runCommandWithCheck' },
convertToCapped: { m: 'runCommandWithCheck', i: 2 },
dataSize: { m: 'aggregate', e: true },
storageSize: { m: 'aggregate', e: true },
Expand Down Expand Up @@ -1995,7 +2008,7 @@ describe('Collection', () => {
watch: { i: 1 }
};
const ignore: (keyof (typeof Collection)['prototype'])[] = [
'getShardDistribution', 'stats', 'isCapped'
'getShardDistribution', 'stats', 'isCapped', 'compactStructuredEncryptionData'
];
const args = [ { query: {} }, {}, { out: 'coll' } ];
beforeEach(() => {
Expand Down Expand Up @@ -2047,7 +2060,9 @@ describe('Collection', () => {
}
});
context('all commands that use other methods', () => {
for (const method of Object.keys(exceptions)) {
for (const method of Object.keys(exceptions).filter(
k => !ignore.includes(k as any)
)) {
const customA = exceptions[method].a || args;
const customM = exceptions[method].m || method;
const customI = exceptions[method].i || 3;
Expand Down
20 changes: 20 additions & 0 deletions packages/shell-api/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,25 @@ export default class Collection extends ShellApiWithMongoClass {
);
}

/**
* Compacts structured encryption data.
*
* @return {Promise}
*/
@returnsPromise
@apiVersions([])
async compactStructuredEncryptionData(): Promise<Document> {
if (!this._mongo._fleOptions) {
throw new MongoshInvalidInputError(
'The "compactStructuredEncryptionData" command requires Mongo instance configured with auto encryption.',
CommonErrors.InvalidArgument
);
}

this._emitCollectionApiCall('compactStructuredEncryptionData');
return await this._database._runCommand({ compactStructuredEncryptionData: this._name });
}

/**
* Converts a collection to capped
*
Expand All @@ -981,6 +1000,7 @@ export default class Collection extends ShellApiWithMongoClass {
}
);
}

/**
* Internal function which calls the Service Provider createIndexes function.
* This function is used also by createIndex and ensureIndex
Expand Down