Skip to content

Commit 018fd02

Browse files
temp commit to prepare for 5.x backport PR
1 parent 60bfc48 commit 018fd02

File tree

3 files changed

+174
-8
lines changed

3 files changed

+174
-8
lines changed

src/cmap/connect.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
type ConnectionOptions,
2626
CryptoConnection
2727
} from './connection';
28-
import type { ClientMetadata } from './handshake/client_metadata';
28+
import { addContainerMetadata } from './handshake/client_metadata';
2929
import {
3030
MAX_SUPPORTED_SERVER_VERSION,
3131
MAX_SUPPORTED_WIRE_VERSION,
@@ -180,7 +180,7 @@ export interface HandshakeDocument extends Document {
180180
ismaster?: boolean;
181181
hello?: boolean;
182182
helloOk?: boolean;
183-
client: ClientMetadata;
183+
client: Document;
184184
compression: string[];
185185
saslSupportedMechs?: string;
186186
loadBalanced?: boolean;
@@ -197,11 +197,12 @@ export async function prepareHandshakeDocument(
197197
const options = authContext.options;
198198
const compressors = options.compressors ? options.compressors : [];
199199
const { serverApi } = authContext.connection;
200+
const clientMetadata = await addContainerMetadata(options.metadata);
200201

201202
const handshakeDoc: HandshakeDocument = {
202203
[serverApi?.version ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
203204
helloOk: true,
204-
client: options.metadata,
205+
client: clientMetadata,
205206
compression: compressors
206207
};
207208

src/cmap/handshake/client_metadata.ts

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { promises as fs } from 'fs';
12
import * as os from 'os';
23
import * as process from 'process';
34

4-
import { BSON, Int32 } from '../../bson';
5+
import { BSON, type Document, Int32 } from '../../bson';
56
import { MongoInvalidArgumentError } from '../../error';
67
import type { MongoOptions } from '../../mongo_client';
78

@@ -71,13 +72,13 @@ export class LimitedSizeDocument {
7172
return true;
7273
}
7374

74-
toObject(): ClientMetadata {
75+
toObject(): Document {
7576
return BSON.deserialize(BSON.serialize(this.document), {
7677
promoteLongs: false,
7778
promoteBuffers: false,
7879
promoteValues: false,
7980
useBigInt64: false
80-
}) as ClientMetadata;
81+
});
8182
}
8283
}
8384

@@ -153,7 +154,62 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
153154
}
154155
}
155156

156-
return metadataDocument.toObject();
157+
return metadataDocument.toObject() as ClientMetadata;
158+
}
159+
160+
let isDocker: boolean;
161+
let dockerPromise: Promise<void>;
162+
/** @internal */
163+
async function getContainerMetadata() {
164+
const containerMetadata: Record<string, any> = {};
165+
if (isDocker == null) {
166+
dockerPromise ??= fs.access('/.dockerenv');
167+
try {
168+
await dockerPromise;
169+
isDocker = true;
170+
} catch {
171+
isDocker = false;
172+
}
173+
}
174+
175+
const { KUBERNETES_SERVICE_HOST = '' } = process.env;
176+
const isKubernetes = KUBERNETES_SERVICE_HOST.length > 0 ? true : false;
177+
178+
if (isDocker) containerMetadata.runtime = 'docker';
179+
if (isKubernetes) containerMetadata.orchestrator = 'kubernetes';
180+
181+
return containerMetadata;
182+
}
183+
184+
/**
185+
* @internal
186+
* Re-add each metadata value.
187+
* Attempt to add new env container metadata, but keep old data if it does not fit.
188+
*/
189+
export async function addContainerMetadata(originalMetadata: ClientMetadata) {
190+
const containerMetadata = await getContainerMetadata();
191+
if (Object.keys(containerMetadata).length === 0) return originalMetadata;
192+
193+
const extendedMetadata = new LimitedSizeDocument(512);
194+
195+
const extendedEnvMetadata = { ...originalMetadata?.env, container: containerMetadata };
196+
197+
for (const [key, val] of Object.entries(originalMetadata)) {
198+
if (key !== 'env') {
199+
extendedMetadata.ifItFitsItSits(key, val);
200+
} else {
201+
if (!extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata)) {
202+
// add in old data if newer / extended metadata does not fit
203+
extendedMetadata.ifItFitsItSits('env', val);
204+
}
205+
}
206+
}
207+
208+
if (!('env' in originalMetadata)) {
209+
extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata);
210+
}
211+
212+
return extendedMetadata.toObject();
157213
}
158214

159215
/**

test/unit/cmap/connect.test.ts

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,116 @@ describe('Connect Tests', function () {
207207
});
208208
});
209209

210-
context('prepareHandshakeDocument', () => {
210+
describe('prepareHandshakeDocument', () => {
211+
describe('client environment (containers and FAAS)', () => {
212+
const cachedEnv = process.env;
213+
214+
context('when only kubernetes is present', () => {
215+
const authContext = {
216+
connection: {},
217+
options: { ...CONNECT_DEFAULTS }
218+
};
219+
220+
beforeEach(() => {
221+
process.env.KUBERNETES_SERVICE_HOST = 'I exist';
222+
});
223+
224+
afterEach(() => {
225+
if (cachedEnv.KUBERNETES_SERVICE_HOST != null) {
226+
process.env.KUBERNETES_SERVICE_HOST = cachedEnv.KUBERNETES_SERVICE_HOST;
227+
} else {
228+
delete process.env.KUBERNETES_SERVICE_HOST;
229+
}
230+
});
231+
232+
it(`should include { orchestrator: 'kubernetes'} in client.env.container`, async () => {
233+
const handshakeDocument = await prepareHandshakeDocument(authContext);
234+
expect(handshakeDocument.client.env.container.orchestrator).to.equal('kubernetes');
235+
});
236+
237+
it(`should not have 'name' property in client.env `, async () => {
238+
const handshakeDocument = await prepareHandshakeDocument(authContext);
239+
expect(handshakeDocument.client.env).to.not.have.property('name');
240+
});
241+
});
242+
243+
context('when kubernetes and FAAS are both present', () => {
244+
const authContext = {
245+
connection: {},
246+
options: { ...CONNECT_DEFAULTS, metadata: { env: { name: 'aws.lambda' } } }
247+
};
248+
249+
beforeEach(() => {
250+
process.env.KUBERNETES_SERVICE_HOST = 'I exist';
251+
});
252+
253+
afterEach(() => {
254+
if (cachedEnv.KUBERNETES_SERVICE_HOST != null) {
255+
process.env.KUBERNETES_SERVICE_HOST = cachedEnv.KUBERNETES_SERVICE_HOST;
256+
} else {
257+
delete process.env.KUBERNETES_SERVICE_HOST;
258+
}
259+
});
260+
261+
it(`should include { orchestrator: 'kubernetes'} in client.env.container`, async () => {
262+
const handshakeDocument = await prepareHandshakeDocument(authContext);
263+
expect(handshakeDocument.client.env.container.orchestrator).to.equal('kubernetes');
264+
});
265+
266+
it(`should still have properly set 'name' property in client.env `, async () => {
267+
const handshakeDocument = await prepareHandshakeDocument(authContext);
268+
expect(handshakeDocument.client.env.name).to.equal('aws.lambda');
269+
});
270+
});
271+
272+
context('when container nor FAAS env is not present (empty string case)', () => {
273+
const authContext = {
274+
connection: {},
275+
options: { ...CONNECT_DEFAULTS }
276+
};
277+
278+
context('when process.env.KUBERNETES_SERVICE_HOST = undefined', () => {
279+
beforeEach(() => {
280+
delete process.env.KUBERNETES_SERVICE_HOST;
281+
});
282+
283+
afterEach(() => {
284+
afterEach(() => {
285+
if (cachedEnv.KUBERNETES_SERVICE_HOST != null) {
286+
process.env.KUBERNETES_SERVICE_HOST = cachedEnv.KUBERNETES_SERVICE_HOST;
287+
} else {
288+
delete process.env.KUBERNETES_SERVICE_HOST;
289+
}
290+
});
291+
});
292+
293+
it(`should not have 'env' property in client`, async () => {
294+
const handshakeDocument = await prepareHandshakeDocument(authContext);
295+
expect(handshakeDocument.client).to.not.have.property('env');
296+
});
297+
});
298+
299+
context('when process.env.KUBERNETES_SERVICE_HOST is an empty string', () => {
300+
beforeEach(() => {
301+
process.env.KUBERNETES_SERVICE_HOST = '';
302+
});
303+
304+
afterEach(() => {
305+
if (cachedEnv.KUBERNETES_SERVICE_HOST != null) {
306+
process.env.KUBERNETES_SERVICE_HOST = cachedEnv.KUBERNETES_SERVICE_HOST;
307+
} else {
308+
delete process.env.KUBERNETES_SERVICE_HOST;
309+
}
310+
});
311+
312+
it(`should not have 'env' property in client`, async () => {
313+
const handshakeDocument = await prepareHandshakeDocument(authContext);
314+
expect(handshakeDocument.client).to.not.have.property('env');
315+
});
316+
});
317+
});
318+
});
319+
211320
context('when serverApi.version is present', () => {
212321
const options = {
213322
authProviders: new MongoClientAuthProviders()

0 commit comments

Comments
 (0)