From 1ea261a8c8747afc319a9a92d89c820861669f81 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Thu, 3 Jul 2025 18:24:44 +0100 Subject: [PATCH 1/5] chore: reinstate telemetry/docker change after revert (MCP-49) --- src/telemetry/telemetry.ts | 33 +++++++++++++++++++++++++---- src/telemetry/types.ts | 1 + tests/integration/telemetry.test.ts | 2 +- tests/unit/telemetry.test.ts | 6 +++--- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/telemetry/telemetry.ts b/src/telemetry/telemetry.ts index ccf0eb41..429f47cb 100644 --- a/src/telemetry/telemetry.ts +++ b/src/telemetry/telemetry.ts @@ -7,6 +7,7 @@ import { MACHINE_METADATA } from "./constants.js"; import { EventCache } from "./eventCache.js"; import nodeMachineId from "node-machine-id"; import { getDeviceId } from "@mongodb-js/device-id"; +import fs from "fs/promises"; type EventResult = { success: boolean; @@ -18,7 +19,7 @@ export const DEVICE_ID_TIMEOUT = 3000; export class Telemetry { private isBufferingEvents: boolean = true; /** Resolves when the device ID is retrieved or timeout occurs */ - public deviceIdPromise: Promise | undefined; + public dataPromise: Promise<[string, boolean]> | undefined; private deviceIdAbortController = new AbortController(); private eventCache: EventCache; private getRawMachineId: () => Promise; @@ -52,11 +53,32 @@ export class Telemetry { return instance; } + private async isContainerEnv(): Promise { + if (process.platform !== "linux") { + return false; // we only support linux containers for now + } + + if (process.env.container) { + return true; + } + + const exists = await Promise.all(["/.dockerenv", "/run/.containerenv", "/var/run/.containerenv"].map(async (file) => { + try { + await fs.access(file); + return true; + } catch { + return false; + } + })); + + return exists.includes(true); + } + private async start(): Promise { if (!this.isTelemetryEnabled()) { return; } - this.deviceIdPromise = getDeviceId({ + this.dataPromise = Promise.all([getDeviceId({ getMachineId: () => this.getRawMachineId(), onError: (reason, error) => { switch (reason) { @@ -72,9 +94,12 @@ export class Telemetry { } }, abortSignal: this.deviceIdAbortController.signal, - }); + }), this.isContainerEnv()]); + + const [deviceId, containerEnv] = await this.dataPromise; - this.commonProperties.device_id = await this.deviceIdPromise; + this.commonProperties.device_id = deviceId; + this.commonProperties.is_container_env = containerEnv; this.isBufferingEvents = false; } diff --git a/src/telemetry/types.ts b/src/telemetry/types.ts index d77cc010..862441fd 100644 --- a/src/telemetry/types.ts +++ b/src/telemetry/types.ts @@ -66,6 +66,7 @@ export type CommonStaticProperties = { */ export type CommonProperties = { device_id?: string; + is_container_env?: boolean; mcp_client_version?: string; mcp_client_name?: string; config_atlas_auth?: TelemetryBoolSet; diff --git a/tests/integration/telemetry.test.ts b/tests/integration/telemetry.test.ts index 522c1154..8f7f8728 100644 --- a/tests/integration/telemetry.test.ts +++ b/tests/integration/telemetry.test.ts @@ -20,7 +20,7 @@ describe("Telemetry", () => { expect(telemetry.getCommonProperties().device_id).toBe(undefined); expect(telemetry["isBufferingEvents"]).toBe(true); - await telemetry.deviceIdPromise; + await telemetry.dataPromise; expect(telemetry.getCommonProperties().device_id).toBe(actualHashedId); expect(telemetry["isBufferingEvents"]).toBe(false); diff --git a/tests/unit/telemetry.test.ts b/tests/unit/telemetry.test.ts index c1ae28ea..74c46b99 100644 --- a/tests/unit/telemetry.test.ts +++ b/tests/unit/telemetry.test.ts @@ -219,7 +219,7 @@ describe("Telemetry", () => { expect(telemetry["isBufferingEvents"]).toBe(true); expect(telemetry.getCommonProperties().device_id).toBe(undefined); - await telemetry.deviceIdPromise; + await telemetry.dataPromise; expect(telemetry["isBufferingEvents"]).toBe(false); expect(telemetry.getCommonProperties().device_id).toBe(hashedMachineId); @@ -235,7 +235,7 @@ describe("Telemetry", () => { expect(telemetry["isBufferingEvents"]).toBe(true); expect(telemetry.getCommonProperties().device_id).toBe(undefined); - await telemetry.deviceIdPromise; + await telemetry.dataPromise; expect(telemetry["isBufferingEvents"]).toBe(false); expect(telemetry.getCommonProperties().device_id).toBe("unknown"); @@ -263,7 +263,7 @@ describe("Telemetry", () => { jest.advanceTimersByTime(DEVICE_ID_TIMEOUT); - await telemetry.deviceIdPromise; + await telemetry.dataPromise; expect(telemetry.getCommonProperties().device_id).toBe("unknown"); expect(telemetry["isBufferingEvents"]).toBe(false); From c5dbd61857512e369d001cf20187a2ccf64bd64f Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Thu, 3 Jul 2025 18:28:48 +0100 Subject: [PATCH 2/5] fix: format --- src/telemetry/telemetry.ts | 55 +++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/telemetry/telemetry.ts b/src/telemetry/telemetry.ts index 429f47cb..36f3d02f 100644 --- a/src/telemetry/telemetry.ts +++ b/src/telemetry/telemetry.ts @@ -62,14 +62,16 @@ export class Telemetry { return true; } - const exists = await Promise.all(["/.dockerenv", "/run/.containerenv", "/var/run/.containerenv"].map(async (file) => { - try { - await fs.access(file); - return true; - } catch { - return false; - } - })); + const exists = await Promise.all( + ["/.dockerenv", "/run/.containerenv", "/var/run/.containerenv"].map(async (file) => { + try { + await fs.access(file); + return true; + } catch { + return false; + } + }) + ); return exists.includes(true); } @@ -78,23 +80,26 @@ export class Telemetry { if (!this.isTelemetryEnabled()) { return; } - this.dataPromise = Promise.all([getDeviceId({ - getMachineId: () => this.getRawMachineId(), - onError: (reason, error) => { - switch (reason) { - case "resolutionError": - logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error)); - break; - case "timeout": - logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out"); - break; - case "abort": - // No need to log in the case of aborts - break; - } - }, - abortSignal: this.deviceIdAbortController.signal, - }), this.isContainerEnv()]); + this.dataPromise = Promise.all([ + getDeviceId({ + getMachineId: () => this.getRawMachineId(), + onError: (reason, error) => { + switch (reason) { + case "resolutionError": + logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error)); + break; + case "timeout": + logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out"); + break; + case "abort": + // No need to log in the case of aborts + break; + } + }, + abortSignal: this.deviceIdAbortController.signal, + }), + this.isContainerEnv(), + ]); const [deviceId, containerEnv] = await this.dataPromise; From dd6dadd525355afb99f5838a3719ac9cf25c0acf Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Thu, 3 Jul 2025 18:34:35 +0100 Subject: [PATCH 3/5] fix: tests --- tests/unit/telemetry.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/unit/telemetry.test.ts b/tests/unit/telemetry.test.ts index 74c46b99..4c4930e9 100644 --- a/tests/unit/telemetry.test.ts +++ b/tests/unit/telemetry.test.ts @@ -138,6 +138,8 @@ describe("Telemetry", () => { it("should send events successfully", async () => { const testEvent = createTestEvent(); + await telemetry.dataPromise; + await telemetry.emitEvents([testEvent]); verifyMockCalls({ @@ -152,6 +154,8 @@ describe("Telemetry", () => { const testEvent = createTestEvent(); + await telemetry.dataPromise; + await telemetry.emitEvents([testEvent]); verifyMockCalls({ @@ -175,6 +179,8 @@ describe("Telemetry", () => { // Set up mock to return cached events mockEventCache.getEvents.mockReturnValueOnce([cachedEvent]); + await telemetry.dataPromise; + await telemetry.emitEvents([newEvent]); verifyMockCalls({ @@ -184,7 +190,9 @@ describe("Telemetry", () => { }); }); - it("should correctly add common properties to events", () => { + it("should correctly add common properties to events", async () => { + await telemetry.dataPromise; + const commonProps = telemetry.getCommonProperties(); // Use explicit type assertion From 8ac8ef3d97d04791f160f2f581c6084f77a5f294 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Fri, 4 Jul 2025 16:27:04 +0100 Subject: [PATCH 4/5] chore: rename --- src/telemetry/telemetry.ts | 10 +++++----- tests/integration/telemetry.test.ts | 2 +- tests/unit/telemetry.test.ts | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/telemetry/telemetry.ts b/src/telemetry/telemetry.ts index 36f3d02f..3f3f7576 100644 --- a/src/telemetry/telemetry.ts +++ b/src/telemetry/telemetry.ts @@ -19,7 +19,7 @@ export const DEVICE_ID_TIMEOUT = 3000; export class Telemetry { private isBufferingEvents: boolean = true; /** Resolves when the device ID is retrieved or timeout occurs */ - public dataPromise: Promise<[string, boolean]> | undefined; + public setupPromise: Promise<[string, boolean]> | undefined; private deviceIdAbortController = new AbortController(); private eventCache: EventCache; private getRawMachineId: () => Promise; @@ -49,7 +49,7 @@ export class Telemetry { ): Telemetry { const instance = new Telemetry(session, userConfig, commonProperties, { eventCache, getRawMachineId }); - void instance.start(); + void instance.setup(); return instance; } @@ -76,11 +76,11 @@ export class Telemetry { return exists.includes(true); } - private async start(): Promise { + private async setup(): Promise { if (!this.isTelemetryEnabled()) { return; } - this.dataPromise = Promise.all([ + this.setupPromise = Promise.all([ getDeviceId({ getMachineId: () => this.getRawMachineId(), onError: (reason, error) => { @@ -101,7 +101,7 @@ export class Telemetry { this.isContainerEnv(), ]); - const [deviceId, containerEnv] = await this.dataPromise; + const [deviceId, containerEnv] = await this.setupPromise; this.commonProperties.device_id = deviceId; this.commonProperties.is_container_env = containerEnv; diff --git a/tests/integration/telemetry.test.ts b/tests/integration/telemetry.test.ts index 8f7f8728..881a8915 100644 --- a/tests/integration/telemetry.test.ts +++ b/tests/integration/telemetry.test.ts @@ -20,7 +20,7 @@ describe("Telemetry", () => { expect(telemetry.getCommonProperties().device_id).toBe(undefined); expect(telemetry["isBufferingEvents"]).toBe(true); - await telemetry.dataPromise; + await telemetry.setupPromise; expect(telemetry.getCommonProperties().device_id).toBe(actualHashedId); expect(telemetry["isBufferingEvents"]).toBe(false); diff --git a/tests/unit/telemetry.test.ts b/tests/unit/telemetry.test.ts index 4c4930e9..1898c4a6 100644 --- a/tests/unit/telemetry.test.ts +++ b/tests/unit/telemetry.test.ts @@ -138,7 +138,7 @@ describe("Telemetry", () => { it("should send events successfully", async () => { const testEvent = createTestEvent(); - await telemetry.dataPromise; + await telemetry.setupPromise; await telemetry.emitEvents([testEvent]); @@ -154,7 +154,7 @@ describe("Telemetry", () => { const testEvent = createTestEvent(); - await telemetry.dataPromise; + await telemetry.setupPromise; await telemetry.emitEvents([testEvent]); @@ -179,7 +179,7 @@ describe("Telemetry", () => { // Set up mock to return cached events mockEventCache.getEvents.mockReturnValueOnce([cachedEvent]); - await telemetry.dataPromise; + await telemetry.setupPromise; await telemetry.emitEvents([newEvent]); @@ -191,7 +191,7 @@ describe("Telemetry", () => { }); it("should correctly add common properties to events", async () => { - await telemetry.dataPromise; + await telemetry.setupPromise; const commonProps = telemetry.getCommonProperties(); @@ -227,7 +227,7 @@ describe("Telemetry", () => { expect(telemetry["isBufferingEvents"]).toBe(true); expect(telemetry.getCommonProperties().device_id).toBe(undefined); - await telemetry.dataPromise; + await telemetry.setupPromise; expect(telemetry["isBufferingEvents"]).toBe(false); expect(telemetry.getCommonProperties().device_id).toBe(hashedMachineId); @@ -243,7 +243,7 @@ describe("Telemetry", () => { expect(telemetry["isBufferingEvents"]).toBe(true); expect(telemetry.getCommonProperties().device_id).toBe(undefined); - await telemetry.dataPromise; + await telemetry.setupPromise; expect(telemetry["isBufferingEvents"]).toBe(false); expect(telemetry.getCommonProperties().device_id).toBe("unknown"); @@ -271,7 +271,7 @@ describe("Telemetry", () => { jest.advanceTimersByTime(DEVICE_ID_TIMEOUT); - await telemetry.dataPromise; + await telemetry.setupPromise; expect(telemetry.getCommonProperties().device_id).toBe("unknown"); expect(telemetry["isBufferingEvents"]).toBe(false); From 905959cd5d86e02f7cc15025b0424f677d672a48 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Fri, 4 Jul 2025 16:28:43 +0100 Subject: [PATCH 5/5] fix: comment --- src/telemetry/telemetry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/telemetry/telemetry.ts b/src/telemetry/telemetry.ts index 3f3f7576..5d0ad827 100644 --- a/src/telemetry/telemetry.ts +++ b/src/telemetry/telemetry.ts @@ -18,7 +18,7 @@ export const DEVICE_ID_TIMEOUT = 3000; export class Telemetry { private isBufferingEvents: boolean = true; - /** Resolves when the device ID is retrieved or timeout occurs */ + /** Resolves when the setup is complete or a timeout occurs */ public setupPromise: Promise<[string, boolean]> | undefined; private deviceIdAbortController = new AbortController(); private eventCache: EventCache;