Skip to content
Merged
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
90 changes: 89 additions & 1 deletion src/env-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import { pickEnvVars } from './env-utils';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { WrapperConfig } from './types';
import { getConfigEnvValue, getLowerCaseProcessEnvValue, pickEnvVars } from './env-utils';

function makeWrapperConfig(overrides: Partial<WrapperConfig> = {}): WrapperConfig {
return {
agentCommand: 'echo test',
allowedDomains: [],
keepContainers: false,
logLevel: 'info',
workDir: '/tmp/env-utils-test',
...overrides,
};
}

describe('pickEnvVars', () => {
let savedEnv: Record<string, string | undefined>;
Expand Down Expand Up @@ -61,3 +76,76 @@ describe('pickEnvVars', () => {
expect(pickEnvVars('TEST_PICK_SINGLE')).toEqual({ TEST_PICK_SINGLE: 'only-one' });
});
});

describe('getConfigEnvValue', () => {
let savedEnv: string | undefined;

beforeEach(() => {
savedEnv = process.env.TEST_CONFIG_ENV_VALUE;
delete process.env.TEST_CONFIG_ENV_VALUE;
});

afterEach(() => {
if (savedEnv !== undefined) {
process.env.TEST_CONFIG_ENV_VALUE = savedEnv;
} else {
delete process.env.TEST_CONFIG_ENV_VALUE;
}
});

it('prefers additionalEnv over envFile and process.env, trimming the result', () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'env-utils-'));
const envFilePath = path.join(tempDir, '.env');
fs.writeFileSync(envFilePath, 'TEST_CONFIG_ENV_VALUE= from-file \n');
process.env.TEST_CONFIG_ENV_VALUE = ' from-process ';

try {
const config = makeWrapperConfig({
additionalEnv: { TEST_CONFIG_ENV_VALUE: ' from-additional ' },
envAll: true,
envFile: envFilePath,
});

expect(getConfigEnvValue(config, 'TEST_CONFIG_ENV_VALUE')).toBe('from-additional');
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});

it('falls back to process.env only when envAll is enabled and omits blank values', () => {
process.env.TEST_CONFIG_ENV_VALUE = ' ';
const config = makeWrapperConfig({ envAll: true });
expect(getConfigEnvValue(config, 'TEST_CONFIG_ENV_VALUE')).toBeUndefined();

process.env.TEST_CONFIG_ENV_VALUE = ' from-process ';
expect(getConfigEnvValue(config, 'TEST_CONFIG_ENV_VALUE')).toBe('from-process');
expect(getConfigEnvValue(makeWrapperConfig({ envAll: false }), 'TEST_CONFIG_ENV_VALUE')).toBeUndefined();
});
});

describe('getLowerCaseProcessEnvValue', () => {
let savedEnv: string | undefined;

beforeEach(() => {
savedEnv = process.env.TEST_LOWERCASE_ENV_VALUE;
delete process.env.TEST_LOWERCASE_ENV_VALUE;
});

afterEach(() => {
if (savedEnv !== undefined) {
process.env.TEST_LOWERCASE_ENV_VALUE = savedEnv;
} else {
delete process.env.TEST_LOWERCASE_ENV_VALUE;
}
});

it('trims and lowercases process env values', () => {
process.env.TEST_LOWERCASE_ENV_VALUE = ' GitHub-OIDC ';
expect(getLowerCaseProcessEnvValue('TEST_LOWERCASE_ENV_VALUE')).toBe('github-oidc');
});

it('returns undefined for blank process env values', () => {
process.env.TEST_LOWERCASE_ENV_VALUE = ' ';
expect(getLowerCaseProcessEnvValue('TEST_LOWERCASE_ENV_VALUE')).toBeUndefined();
});
});
23 changes: 23 additions & 0 deletions src/env-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
import { readEnvFile } from './github-env';
import { WrapperConfig } from './types';

export function normalizeEnvValue(value: string | undefined): string | undefined {
const normalizedValue = value?.trim();
return normalizedValue || undefined;
}

export function getConfigEnvValue(config: WrapperConfig, key: string): string | undefined {
const envFileValue = config.envFile
? readEnvFile(config.envFile)[key]
: undefined;
const value =
config.additionalEnv?.[key] ??
envFileValue ??
(config.envAll ? process.env[key] : undefined);
return normalizeEnvValue(value);
}

export function getLowerCaseProcessEnvValue(key: string): string | undefined {
return normalizeEnvValue(process.env[key])?.toLowerCase();
}

/**
* Returns an object containing only the specified environment variable names
* that are currently set (non-empty) in `process.env`.
Expand Down
18 changes: 3 additions & 15 deletions src/services/api-proxy-credential-env.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { logger } from '../logger';
import { WrapperConfig, API_PROXY_PORTS } from '../types';
import { readEnvFile } from '../github-env';
import { COPILOT_PLACEHOLDER_TOKEN } from '../constants/placeholders';
import { getConfigEnvValue, getLowerCaseProcessEnvValue } from '../env-utils';
import { NetworkConfig } from './squid-service';

interface ApiProxyCredentialEnvParams {
Expand All @@ -14,18 +14,6 @@ interface ApiProxyCredentialEnvParams {
// are runtime-configurable and not limited to a fixed allowlist.
const RESPONSES_WIRE_API_MODEL_PATTERN = /(^|[/:])(gpt-5|o3)([-_.]|$)/i;

function getConfigEnvValue(config: WrapperConfig, key: string): string | undefined {
const envFileValue = config.envFile
? readEnvFile(config.envFile)[key]
: undefined;
const value =
config.additionalEnv?.[key] ??
envFileValue ??
(config.envAll ? process.env[key] : undefined);
const normalizedValue = value?.trim();
return normalizedValue || undefined;
}

function requiresResponsesWireApi(copilotModel: string): boolean {
return RESPONSES_WIRE_API_MODEL_PATTERN.test(copilotModel);
}
Expand All @@ -35,8 +23,8 @@ export function buildAgentCredentialEnv(params: ApiProxyCredentialEnvParams): Re
if (!networkConfig.proxyIp) {
throw new Error('buildAgentCredentialEnv: networkConfig.proxyIp is required');
}
const normalizedAuthType = (process.env.AWF_AUTH_TYPE || '').trim().toLowerCase();
const normalizedAuthProvider = (process.env.AWF_AUTH_PROVIDER || '').trim().toLowerCase();
const normalizedAuthType = getLowerCaseProcessEnvValue('AWF_AUTH_TYPE') || '';
const normalizedAuthProvider = getLowerCaseProcessEnvValue('AWF_AUTH_PROVIDER') || '';
const shouldProxyAnthropic = Boolean(config.anthropicApiKey || (normalizedAuthType === 'github-oidc' && normalizedAuthProvider === 'anthropic'));

const agentEnvAdditions: Record<string, string> = {
Expand Down
17 changes: 2 additions & 15 deletions src/services/api-proxy-service-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import {
SQUID_PORT,
} from '../constants';
import { stripScheme } from '../host-env';
import { readEnvFile } from '../github-env';
import { buildRuntimeImageRef } from '../image-tag';
import { WrapperConfig, API_PROXY_HEALTH_PORT } from '../types';
import { pickEnvVars } from '../env-utils';
import { getConfigEnvValue, getLowerCaseProcessEnvValue, pickEnvVars } from '../env-utils';
import { NetworkConfig, ImageBuildConfig } from './squid-service';
import { applyHostPathPrefixToVolumes } from './host-path-prefix';
import { buildContainerSecurityHardening } from './service-security';
Expand Down Expand Up @@ -72,25 +71,13 @@ function resolveProviderSessionId(config: WrapperConfig): string | undefined {
return normalizedValue || undefined;
}

function getConfigEnvValue(config: WrapperConfig, key: string): string | undefined {
const envFileValue = config.envFile
? readEnvFile(config.envFile)[key]
: undefined;
const value =
config.additionalEnv?.[key] ??
envFileValue ??
(config.envAll ? process.env[key] : undefined);
const normalizedValue = value?.trim();
return normalizedValue || undefined;
}

export function buildApiProxyServiceConfig(params: ApiProxyServiceConfigParams): any {
const { config, networkConfig, apiProxyLogsPath, imageConfig } = params;
if (!networkConfig.proxyIp) {
throw new Error('buildApiProxyServiceConfig: networkConfig.proxyIp is required');
}
const { useGHCR, registry, parsedTag, projectRoot } = imageConfig;
const normalizedAuthType = (process.env.AWF_AUTH_TYPE || '').trim().toLowerCase();
const normalizedAuthType = getLowerCaseProcessEnvValue('AWF_AUTH_TYPE') || '';

const proxyService: any = {
container_name: API_PROXY_CONTAINER_NAME,
Expand Down
Loading