Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion packages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typescript": "^5.1.6"
}
},
"packageManager": "[email protected]+sha512.ff4579ab459bb25aa7c0ff75b62acebe576f6084b36aa842971cf250a5d8c6cd3bc9420b22ce63c7f93a0857bc6ef29291db39c3e7a23aab5adfd5a4dd6c5d71"
}
151 changes: 151 additions & 0 deletions packages/packages/client/__tests__/deps.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Mock the modules before importing anything else
jest.mock('fs');
jest.mock('shelljs');

describe('Docker dependency detection', () => {
const originalEnv = process.env;

beforeEach(() => {
jest.clearAllMocks();
jest.resetModules(); // Clear module cache

// Reset environment to original state
process.env = { ...originalEnv };

// Explicitly remove any environment variables that could affect tests
delete process.env.KUBERNETES_SERVICE_HOST;
delete process.env.STARSHIP_SKIP_DOCKER_CHECK;
});

afterEach(() => {
process.env = originalEnv;
});

test('should detect Docker via .dockerenv file', async () => {
const fs = await import('fs');
const shell = await import('shelljs');

fs.existsSync = jest.fn().mockImplementation((path: string) => {
return path === '/.dockerenv';
});

shell.cat = jest.fn().mockReturnValue({
includes: (_str: string) => false,
toString: () => ''
});

shell.which = jest.fn().mockImplementation((cmd: string) => {
if (cmd === 'kubectl' || cmd === 'helm') return '/usr/bin/' + cmd;
return null;
});

const { dependencies } = await import('../src/deps');
const dockerDep = dependencies.find((d: any) => d.name === 'docker');

expect(dockerDep).toBeDefined();
expect(dockerDep.installed).toBe(true);
});

test('should detect Docker via cgroup', async () => {
const fs = await import('fs');
const shell = await import('shelljs');

fs.existsSync = jest.fn().mockImplementation((path: string) => {
// /proc/1/cgroup exists but /.dockerenv doesn't
return path === '/proc/1/cgroup';
});

shell.cat = jest.fn().mockReturnValue({
includes: (str: string) => str === 'docker',
toString: () => '1:name=systemd:/docker/abc123'
});

shell.which = jest.fn().mockImplementation((cmd: string) => {
if (cmd === 'kubectl' || cmd === 'helm') return '/usr/bin/' + cmd;
return null;
});

const { dependencies } = await import('../src/deps');
const dockerDep = dependencies.find((d: any) => d.name === 'docker');

expect(dockerDep).toBeDefined();
expect(dockerDep.installed).toBe(true);
});

test('should detect Kubernetes pod environment', async () => {
const fs = await import('fs');
const shell = await import('shelljs');

process.env.KUBERNETES_SERVICE_HOST = '10.0.0.1';

fs.existsSync = jest.fn().mockReturnValue(false);

shell.cat = jest.fn().mockReturnValue({
includes: (_str: string) => false,
toString: () => ''
});

shell.which = jest.fn().mockImplementation((cmd: string) => {
if (cmd === 'kubectl' || cmd === 'helm') return '/usr/bin/' + cmd;
return null;
});

const { dependencies } = await import('../src/deps');
const dockerDep = dependencies.find((d: any) => d.name === 'docker');

expect(dockerDep).toBeDefined();
expect(dockerDep.installed).toBe(true);

// Clean up
delete process.env.KUBERNETES_SERVICE_HOST;
});

test('should require Docker when not in container', async () => {
const fs = await import('fs');
const shell = await import('shelljs');

// Ensure we're not detected as running in a container
fs.existsSync = jest.fn().mockReturnValue(false); // No .dockerenv file

shell.cat = jest.fn().mockReturnValue({
includes: (_str: string) => false, // No docker in cgroup
toString: () => 'some-other-cgroup-content'
});

delete process.env.KUBERNETES_SERVICE_HOST; // Not in Kubernetes
delete process.env.STARSHIP_SKIP_DOCKER_CHECK; // Don't skip the check

shell.which = jest.fn().mockImplementation((cmd: string) => {
if (cmd === 'kubectl' || cmd === 'helm') return '/usr/bin/' + cmd;
return null; // Docker not installed
});

const { dependencies } = await import('../src/deps');
const dockerDep = dependencies.find((d: any) => d.name === 'docker');

expect(dockerDep).toBeDefined();
expect(dockerDep.installed).toBe(false);
});

test('should detect Docker binary when available', async () => {
const fs = await import('fs');
const shell = await import('shelljs');

fs.existsSync = jest.fn().mockReturnValue(false);

shell.cat = jest.fn().mockReturnValue({
includes: (_str: string) => false,
toString: () => ''
});

shell.which = jest.fn().mockImplementation((cmd: string) => {
return '/usr/bin/' + cmd; // All binaries available
});

const { dependencies } = await import('../src/deps');
const dockerDep = dependencies.find((d: any) => d.name === 'docker');

expect(dockerDep).toBeDefined();
expect(dockerDep.installed).toBe(true);
});
});
46 changes: 45 additions & 1 deletion packages/packages/client/src/deps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { existsSync } from 'fs';
import * as shell from 'shelljs';

export type Dependency = {
Expand All @@ -7,6 +8,49 @@ export type Dependency = {
installed: boolean;
};

// Function to detect if we're running inside a Docker container
function isRunningInDocker(): boolean {
// Check for .dockerenv file
if (existsSync('/.dockerenv')) {
return true;
}

// Check cgroup for docker signatures (Linux only)
if (existsSync('/proc/1/cgroup')) {
try {
const cgroup = shell.cat('/proc/1/cgroup');
if (cgroup && cgroup.includes('docker')) {
return true;
}
} catch (e) {
// Ignore errors when reading cgroup
}
}

// Check for KUBERNETES_SERVICE_HOST which indicates we're in a K8s pod
if (process.env.KUBERNETES_SERVICE_HOST) {
return true;
}

return false;
}

// Check if Docker dependency should be skipped
function shouldSkipDocker(): boolean {
// Allow explicit skip via environment variable
if (process.env.STARSHIP_SKIP_DOCKER_CHECK === 'true') {
return true;
}

// Skip if we're running inside a container
if (isRunningInDocker()) {
console.log('Running inside a container, skipping Docker dependency check');
return true;
}

return false;
}

export const dependencies: Dependency[] = [
{
name: 'kubectl',
Expand All @@ -18,7 +62,7 @@ export const dependencies: Dependency[] = [
name: 'docker',
url: 'https://docs.docker.com/get-docker/',
macUrl: 'https://docs.docker.com/desktop/install/mac-install/',
installed: !!shell.which('docker')
installed: shouldSkipDocker() || !!shell.which('docker')
},
{
name: 'helm',
Expand Down
Loading