From b5a0204f7be9ea35fba6b377e51717194b54365f Mon Sep 17 00:00:00 2001 From: Lasim Date: Sun, 5 Jan 2025 20:40:52 +0100 Subject: [PATCH] feat(env): enhance environment variable resolution with default values --- src/utils/normalizeEnvironment.ts | 13 +++++- src/utils/parseEnvironmentVariables.ts | 27 ++++++++---- .../processEnvironmentVariablesGeneration.ts | 28 +++++++++---- src/utils/resolveEnvironmentValue.ts | 22 ++++++++++ .../sample-docker-compose-6.yml | 34 +++++++++++++++ .../sample-docker-compose-7.yml | 41 +++++++++++++++++++ 6 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 src/utils/resolveEnvironmentValue.ts create mode 100644 test/docker-compose-files/sample-docker-compose-6.yml create mode 100644 test/docker-compose-files/sample-docker-compose-7.yml diff --git a/src/utils/normalizeEnvironment.ts b/src/utils/normalizeEnvironment.ts index ab93e20..4f8cadc 100644 --- a/src/utils/normalizeEnvironment.ts +++ b/src/utils/normalizeEnvironment.ts @@ -7,8 +7,17 @@ type EnvironmentInput = string[] | { [key: string]: string } | string | undefine function substituteEnvVariables(value: string, envVariables?: Record): string { if (!envVariables) return value; - return value.replace(/\${([^}]+)}/g, (match, varName) => { - return envVariables[varName] || match; + return value.replace(/\${([^}]+)}/g, (match, p1) => { + // Check if this is a variable with default value + const defaultMatch = p1.match(/([^:-]+):-(.+)/); + + if (defaultMatch) { + const [, varName, defaultValue] = defaultMatch; + return envVariables[varName] || defaultValue; + } + + // Regular variable without default + return envVariables[p1] || match; }); } diff --git a/src/utils/parseEnvironmentVariables.ts b/src/utils/parseEnvironmentVariables.ts index 3fea886..13b1881 100644 --- a/src/utils/parseEnvironmentVariables.ts +++ b/src/utils/parseEnvironmentVariables.ts @@ -1,8 +1,13 @@ -export function parseEnvironmentVariables(environment: Record | string[] | undefined): Record { - const environmentVariables: Record = {}; +import { resolveEnvironmentValue } from './resolveEnvironmentValue'; + +export function parseEnvironmentVariables( + environment: Record | string[] | undefined, + environmentVariables?: Record +): Record { + const environmentResult: Record = {}; if (!environment) { - return environmentVariables; + return environmentResult; } if (Array.isArray(environment)) { @@ -10,20 +15,24 @@ export function parseEnvironmentVariables(environment: Record | str environment.forEach(env => { if (typeof env === 'string' && env.includes('=')) { const [key, value] = env.split('='); - environmentVariables[key.trim()] = value.trim(); + environmentResult[key.trim()] = resolveEnvironmentValue(value.trim(), environmentVariables); } }); } else { // Handle object format: { KEY: "value", OTHER_KEY: "othervalue" } Object.entries(environment).forEach(([key, value]) => { - if (typeof value === 'string' && value.includes('=')) { - const [splitKey, splitValue] = value.split('='); - environmentVariables[splitKey.trim()] = splitValue.trim(); + if (typeof value === 'string') { + if (value.includes('=')) { + const [splitKey, splitValue] = value.split('='); + environmentResult[splitKey.trim()] = resolveEnvironmentValue(splitValue.trim(), environmentVariables); + } else { + environmentResult[key.trim()] = resolveEnvironmentValue(value, environmentVariables); + } } else { - environmentVariables[key.trim()] = value?.toString() || ''; + environmentResult[key.trim()] = value?.toString() || ''; } }); } - return environmentVariables; + return environmentResult; } diff --git a/src/utils/processEnvironmentVariablesGeneration.ts b/src/utils/processEnvironmentVariablesGeneration.ts index b924616..7c1f419 100644 --- a/src/utils/processEnvironmentVariablesGeneration.ts +++ b/src/utils/processEnvironmentVariablesGeneration.ts @@ -124,7 +124,22 @@ export function processEnvironmentVariablesGeneration( } const result = { ...environment }; - const imageConfig = config[image.repository]; + + // Try different repository name formats + const possibleRepoNames = [ + image.repository, + `library/${image.repository}`, + `docker.io/library/${image.repository}`, + `docker.io/${image.repository}` + ]; + + let imageConfig; + for (const repoName of possibleRepoNames) { + if (config[repoName]) { + imageConfig = config[repoName]; + break; + } + } if (!imageConfig) { return result; @@ -139,14 +154,9 @@ export function processEnvironmentVariablesGeneration( const versionConfig = imageConfig.versions[matchingVersion]; - // Process each environment variable - for (const [key, value] of Object.entries(result)) { - if (versionConfig.environment[key]) { - // If the value starts with ${ and ends with }, generate a new value - if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) { - result[key] = generateValue(versionConfig.environment[key]); - } - } + // Process each environment variable that has a generation rule + for (const [envKey, genConfig] of Object.entries(versionConfig.environment)) { + result[envKey] = generateValue(genConfig); } return result; diff --git a/src/utils/resolveEnvironmentValue.ts b/src/utils/resolveEnvironmentValue.ts new file mode 100644 index 0000000..a02e925 --- /dev/null +++ b/src/utils/resolveEnvironmentValue.ts @@ -0,0 +1,22 @@ +export function resolveEnvironmentValue( + value: string, + environmentVariables?: Record +): string { + + // Check if value uses ${VAR:-default} syntax + const defaultValueMatch = value.match(/\${([^:-]+):-([^}]+)}/); + + if (defaultValueMatch) { + const [, varName, defaultValue] = defaultValueMatch; + + // Check if the variable exists in provided environment variables + if (environmentVariables && varName in environmentVariables) { + return environmentVariables[varName]; + } + + // No environment variable found, use default + return defaultValue; + } + + return value; +} diff --git a/test/docker-compose-files/sample-docker-compose-6.yml b/test/docker-compose-files/sample-docker-compose-6.yml new file mode 100644 index 0000000..16c2dc4 --- /dev/null +++ b/test/docker-compose-files/sample-docker-compose-6.yml @@ -0,0 +1,34 @@ +version: '3.3' +networks: + yabin-network: + name: yabin-network + ipam: + driver: default +services: + yabin: + container_name: yabin + ports: + - '${PORT:-3000}:${PORT:-3000}' + image: 'yureien/yabin:latest' + env_file: .env + depends_on: + - db + networks: + - yabin-network + db: + container_name: yabin-db + restart: always + image: postgres:15-alpine + env_file: .env + expose: + - '5432' + environment: + POSTGRES_USER: ${DB_USER:-yabin_user} + POSTGRES_PASSWORD: ${DB_USER_PASS:-123} + POSTGRES_DB: ${DB_NAME:-yabin_db} + POSTGRES_HOST_AUTH_METHOD: "trust" + volumes: + - ./db-data:/var/lib/postgresql/data + networks: + - yabin-network + \ No newline at end of file diff --git a/test/docker-compose-files/sample-docker-compose-7.yml b/test/docker-compose-files/sample-docker-compose-7.yml new file mode 100644 index 0000000..bfb896b --- /dev/null +++ b/test/docker-compose-files/sample-docker-compose-7.yml @@ -0,0 +1,41 @@ +version: '3.3' + +volumes: + db-data: + +services: + typebot-db: + image: postgres:16 + restart: always + volumes: + - db-data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=typebot + - POSTGRES_PASSWORD=typebot + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + typebot-builder: + image: baptistearno/typebot-builder:latest + restart: always + depends_on: + typebot-db: + condition: service_healthy + ports: + - '8080:3000' + extra_hosts: + - 'host.docker.internal:host-gateway' + env_file: .env + + typebot-viewer: + image: baptistearno/typebot-viewer:latest + depends_on: + typebot-db: + condition: service_healthy + restart: always + ports: + - '8081:3000' + env_file: .env + \ No newline at end of file