Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/cmap/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
type ConnectionOptions,
CryptoConnection
} from './connection';
import type { ClientMetadata } from './handshake/client_metadata';
import { addContainerMetadata } from './handshake/client_metadata';
import {
MAX_SUPPORTED_SERVER_VERSION,
MAX_SUPPORTED_WIRE_VERSION,
Expand Down Expand Up @@ -183,7 +183,7 @@ export interface HandshakeDocument extends Document {
ismaster?: boolean;
hello?: boolean;
helloOk?: boolean;
client: ClientMetadata;
client: Document;
compression: string[];
saslSupportedMechs?: string;
loadBalanced?: boolean;
Expand All @@ -200,11 +200,12 @@ export async function prepareHandshakeDocument(
const options = authContext.options;
const compressors = options.compressors ? options.compressors : [];
const { serverApi } = authContext.connection;
const clientMetadata = await addContainerMetadata(options.metadata);

const handshakeDoc: HandshakeDocument = {
[serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
helloOk: true,
client: options.metadata,
client: clientMetadata,
compression: compressors
};

Expand Down
54 changes: 50 additions & 4 deletions src/cmap/handshake/client_metadata.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { promises as fs } from 'fs';
import * as os from 'os';
import * as process from 'process';

import { BSON, Int32 } from '../../bson';
import { BSON, type Document, Int32 } from '../../bson';
import { MongoInvalidArgumentError } from '../../error';
import type { MongoOptions } from '../../mongo_client';

Expand Down Expand Up @@ -71,13 +72,13 @@ export class LimitedSizeDocument {
return true;
}

toObject(): ClientMetadata {
toObject(): Document {
return BSON.deserialize(BSON.serialize(this.document), {
promoteLongs: false,
promoteBuffers: false,
promoteValues: false,
useBigInt64: false
}) as ClientMetadata;
});
}
}

Expand Down Expand Up @@ -152,8 +153,53 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
}
}
}
return metadataDocument.toObject() as ClientMetadata;
}

let isDocker: boolean;
let dockerPromise: any;
/** @internal */
export async function addContainerMetadata(originalMetadata: ClientMetadata) {
async function getContainerMetadata() {
const containerMetadata: Record<string, any> = {};
if (isDocker == null) {
dockerPromise ??= fs.access('/.dockerenv');
try {
await dockerPromise;
isDocker = true;
} catch {
isDocker = false;
}
}
const isKubernetes = process.env.KUBERNETES_SERVICE_HOST ? true : false;

if (isDocker) containerMetadata['runtime'] = 'docker';
if (isKubernetes) containerMetadata['orchestrator'] = 'kubernetes';

return containerMetadata;
}

const containerMetadata = await getContainerMetadata();
if (Object.keys(containerMetadata).length === 0) return originalMetadata;

const extendedMetadata = new LimitedSizeDocument(512);
const envMetadata = { ...originalMetadata?.env, container: containerMetadata };

for (const [key, val] of Object.entries(originalMetadata)) {
if (key !== 'env') {
extendedMetadata.ifItFitsItSits(key, val);
} else {
if (!extendedMetadata.ifItFitsItSits('env', envMetadata)) {
extendedMetadata.ifItFitsItSits('env', val);
}
}
}

if (!('env' in originalMetadata)) {
extendedMetadata.ifItFitsItSits('env', envMetadata);
}

return metadataDocument.toObject();
return extendedMetadata.toObject();
}

/**
Expand Down
53 changes: 46 additions & 7 deletions test/unit/cmap/connect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const CONNECT_DEFAULTS = {
loadBalanced: false
};

describe('Connect Tests', function () {
describe('Connect Tests', async function () {
context('when PLAIN auth enabled', () => {
const test: {
server?: any;
Expand Down Expand Up @@ -185,9 +185,48 @@ describe('Connect Tests', function () {
expect(error).to.be.instanceOf(MongoNetworkError);
});

context('prepareHandshakeDocument', () => {
context('prepareHandshakeDocument', async () => {
context('when container is present', async () => {
const authContext = {
connection: {},
options: { ...CONNECT_DEFAULTS }
};

context('when only kubernetes is present', async () => {
beforeEach(() => {
process.env.KUBERNETES_SERVICE_HOST = 'I exist';
});

afterEach(() => {
process.env.KUBERNETES_SERVICE_HOST = '';
});

it(`should include { orchestrator: 'kubernetes'} in client.env.container`, async () => {
const handshakeDocument = await prepareHandshakeDocument(authContext);
expect(handshakeDocument.client.env.container.orchestrator).to.equal('kubernetes');
});

it(`should not have 'name' property in client.env `, async () => {
const handshakeDocument = await prepareHandshakeDocument(authContext);
expect(handshakeDocument.client.env).to.not.have.property('name');
});
});
});

context('when container nor FAAS env is not present', async () => {
const authContext = {
connection: {},
options: { ...CONNECT_DEFAULTS }
};

it(`should not have 'env' property in client`, async () => {
const handshakeDocument = await prepareHandshakeDocument(authContext);
expect(handshakeDocument.client).to.not.have.property('env');
});
});

context('when serverApi.version is present', () => {
const options = {};
const options = { ...CONNECT_DEFAULTS };
const authContext = {
connection: { serverApi: { version: '1' } },
options
Expand All @@ -200,7 +239,7 @@ describe('Connect Tests', function () {
});

context('when serverApi is not present', () => {
const options = {};
const options = { ...CONNECT_DEFAULTS };
const authContext = {
connection: {},
options
Expand All @@ -216,7 +255,7 @@ describe('Connect Tests', function () {
context('when loadBalanced is not set as an option', () => {
const authContext = {
connection: {},
options: {}
options: { ...CONNECT_DEFAULTS }
};

it('does not set loadBalanced on the handshake document', async () => {
Expand All @@ -238,7 +277,7 @@ describe('Connect Tests', function () {
context('when loadBalanced is set to false', () => {
const authContext = {
connection: {},
options: { loadBalanced: false }
options: { ...CONNECT_DEFAULTS, loadBalanced: false }
};

it('does not set loadBalanced on the handshake document', async () => {
Expand All @@ -260,7 +299,7 @@ describe('Connect Tests', function () {
context('when loadBalanced is set to true', () => {
const authContext = {
connection: {},
options: { loadBalanced: true }
options: { ...CONNECT_DEFAULTS, loadBalanced: true }
};

it('sets loadBalanced on the handshake document', async () => {
Expand Down