Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 0 additions & 1 deletion extensions/mssql/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ export const outputContentTypeShowError = "showError";
export const outputContentTypeShowWarning = "showWarning";
export const outputServiceLocalhost = "http://localhost:";
export const localhost = "localhost";
export const localhostIP = "127.0.0.1";
export const defaultContainerName = "sql_server_container";
export const msgContentProviderSqlOutputHtml = "dist/html/sqlOutput.ejs";
export const contentProviderMinFile = "dist/js/app.min.js";
Expand Down
48 changes: 10 additions & 38 deletions extensions/mssql/src/deployment/dockerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import {
defaultPortNumber,
docker,
dockerDeploymentLoggerChannelName,
localhost,
localhostIP,
Platform,
windowsDockerDesktopExecutable,
x64,
Expand Down Expand Up @@ -131,6 +129,10 @@ export const COMMANDS = {
command: "docker",
args: ["ps", "-a", "--format", "{{.Names}}"],
}),
GET_CONTAINER_NAME_FROM_ID: (containerId: string): DockerCommand => ({
command: "docker",
args: ["ps", "-a", "--filter", `id=${containerId}`, "--format", "{{.Names}}"],
}),
INSPECT: (id: string): DockerCommand => ({
command: "docker",
args: ["inspect", sanitizeContainerInput(id)],
Expand All @@ -151,11 +153,11 @@ export const COMMANDS = {
"-e",
"ACCEPT_EULA=Y",
"-e",
`SA_PASSWORD=${password}`,
`\'SA_PASSWORD=${password}\'`,
"-p",
`${port}:${defaultPortNumber}`,
`\'${port}:${defaultPortNumber}\'`,
"--name",
sanitizeContainerInput(name),
`\'${sanitizeContainerInput(name)}\'`,
];

if (hostname) {
Expand Down Expand Up @@ -915,43 +917,13 @@ async function getUsedPortsFromContainers(containerIds: string[]): Promise<Set<n
return usedPorts;
}

/**
* Finds a Docker container by checking if its exposed ports match the server name.
* It inspects each container to find a match with the server name.
*/
async function findContainerByPort(containerIds: string[], serverName: string): Promise<string> {
if (serverName === localhost || serverName === localhostIP) {
serverName += `,${defaultPortNumber}`;
}
for (const id of containerIds) {
try {
const inspect = await execDockerCommand(COMMANDS.INSPECT_CONTAINER(id));
const ports = inspect.match(/"HostPort":\s*"(\d+)"/g);

if (ports?.some((p) => serverName.includes(p.match(/\d+/)?.[0] || ""))) {
const nameMatch = inspect.match(/"Name"\s*:\s*"\/([^"]+)"/);
if (nameMatch) return nameMatch[1];
}
} catch {
// skip container if inspection fails
}
}

return undefined;
}

/**
* Checks if a connection is a Docker container by inspecting the server name.
*/
export async function checkIfConnectionIsDockerContainer(serverName: string): Promise<string> {
if (!serverName.includes(localhost) && !serverName.includes(localhostIP)) return "";

export async function checkIfConnectionIsDockerContainer(machineName: string): Promise<string> {
try {
const stdout = await execDockerCommand(COMMANDS.GET_CONTAINERS());
const containerIds = stdout.split("\n").filter(Boolean);
if (!containerIds.length) return undefined;

return await findContainerByPort(containerIds, serverName);
const stdout = await execDockerCommand(COMMANDS.GET_CONTAINER_NAME_FROM_ID(machineName));
return stdout.trim();
} catch {
return undefined;
}
Expand Down
29 changes: 14 additions & 15 deletions extensions/mssql/src/objectExplorer/objectExplorerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,21 +710,6 @@ export class ObjectExplorerService {
return undefined;
}

// Check if connection is a Docker container
const serverName = connectionProfile.connectionString
? connectionProfile.connectionString.match(/^Server=([^;]+)/)?.[1]
: connectionProfile.server;

if (serverName && !connectionProfile.containerName) {
const containerName = await checkIfConnectionIsDockerContainer(serverName);
if (containerName) {
connectionProfile.containerName = containerName;
}

// if the connnection is a docker container, make sure to set the container name for future use
await this._connectionManager.connectionStore.saveProfile(connectionProfile);
}

if (!connectionProfile.id) {
connectionProfile.id = Utils.generateGuid();
}
Expand Down Expand Up @@ -779,6 +764,20 @@ export class ObjectExplorerService {
return;
}

if (!connectionProfile.containerName) {
const serverInfo = this._connectionManager.getServerInfo(connectionProfile);
let machineName = "";
if (serverInfo) {
machineName = (serverInfo as any)["machineName"];
}
const containerName = await checkIfConnectionIsDockerContainer(machineName);
if (containerName) {
connectionProfile.containerName = containerName;
// if the connection is a docker container, make sure to set the container name for future use
await this._connectionManager.connectionStore.saveProfile(connectionProfile);
}
}

let connectionNode = this.getConnectionNodeFromProfile(connectionProfile);

let isNewConnection = false;
Expand Down
24 changes: 9 additions & 15 deletions extensions/mssql/test/unit/dockerUtilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1097,31 +1097,25 @@ suite("Docker Utilities", () => {
}),
});

// 1. Non-localhost server: should return ""
let result = await dockerUtils.checkIfConnectionIsDockerContainer("some.remote.host");
assert.strictEqual(result, "", "Should return empty string for non-localhost address");

// 2. Docker command fails: should return undefined
// 1. Docker command fails: should return undefined
spawnStub.returns(createFailureProcess(new Error("spawn failed")) as any);
result = await dockerUtils.checkIfConnectionIsDockerContainer("localhost");
let result = await dockerUtils.checkIfConnectionIsDockerContainer("dockercontainerid");
assert.strictEqual(result, undefined, "Should return undefined on spawn failure");

// Reset spawnStub for next test
spawnStub.resetHistory();
spawnStub.returns(createSuccessProcess("") as any); // simulate no containers

// 3. Docker command returns no containers: should return undefined
result = await dockerUtils.checkIfConnectionIsDockerContainer("127.0.0.1");
assert.strictEqual(result, undefined, "Should return undefined when no containers exist");
// 2. Docker command returns no containers: should return empty string
result = await dockerUtils.checkIfConnectionIsDockerContainer("dockercontainerid");
assert.strictEqual(result, "", "Should return empty string when no containers exist");

// 4. Containers exist and one matches the port: should return the container id
// 3. Containers exist and one matches the port: should return the container id
spawnStub.resetHistory();
spawnStub.returns(
createSuccessProcess(`"HostPort": "1433", "Name": "/testContainer",\n`) as any,
); // simulate container with port 1433
spawnStub.returns(createSuccessProcess(`dockercontainerid`) as any); // simulate container with port 1433

result = await dockerUtils.checkIfConnectionIsDockerContainer("localhost, 1433");
assert.strictEqual(result, "testContainer", "Should return matched container ID");
result = await dockerUtils.checkIfConnectionIsDockerContainer("dockercontainerid");
assert.ok(result, "Should return container name");
});

test("findAvailablePort: should find next available port", async () => {
Expand Down
Loading