diff --git a/apps/cli/src/command/diff.command.ts b/apps/cli/src/command/diff.command.ts index 98c97ff..725566f 100644 --- a/apps/cli/src/command/diff.command.ts +++ b/apps/cli/src/command/diff.command.ts @@ -15,6 +15,7 @@ import { filterResourceType, loadBackend, mergeKVConfigurations, + recursiveReplaceEnvVars, toConfiguration, toKVConfiguration, } from './utils'; @@ -87,6 +88,12 @@ export const LoadLocalConfigurationTask = ( ctx.local = toConfiguration(localKVConfiguration); }, }, + { + title: 'Resolve value variables', + task: async () => { + ctx.local = recursiveReplaceEnvVars(ctx.local); + }, + }, { title: 'Filter configuration resource type', enabled: () => diff --git a/apps/cli/src/command/utils.spec.ts b/apps/cli/src/command/utils.spec.ts index 5a8a172..a7e3e3a 100644 --- a/apps/cli/src/command/utils.spec.ts +++ b/apps/cli/src/command/utils.spec.ts @@ -1,6 +1,10 @@ import * as ADCSDK from '@api7/adc-sdk'; -import { fillLabels, recursiveRemoveMetadataField } from './utils'; +import { + fillLabels, + recursiveRemoveMetadataField, + recursiveReplaceEnvVars, +} from './utils'; describe('CLI utils', () => { it('should fill label selector for local resources', () => { @@ -249,4 +253,83 @@ describe('CLI utils', () => { ssls: [{ certificates: [], snis: ['test'] }], }); }); + + describe('Environment Variables', () => { + it('mock config', () => { + const config: ADCSDK.Configuration = { + services: [ + { + name: 'Test ${NAME}', + routes: [ + { + name: 'Test ${NAME}', + uris: ['/test/${NAME}'], + }, + ], + }, + ], + consumers: [ + { + username: 'TEST_${NAME}', + plugins: { + 'key-auth': { + key: '${SECRET}', + }, + }, + }, + ], + ssls: [ + { + snis: ['test.com'], + certificates: [ + { + certificate: '${CERT}', + key: '${KEY}', + }, + ], + }, + ], + global_rules: { + // object key contains variables will not be parsed + '${GLOBAL_PLUGIN}': { + key: '${SECRET}', + }, + }, + plugin_metadata: { + 'file-logger': { + log_format: { + note: '${NOTE}', + }, + }, + }, + }; + expect( + recursiveReplaceEnvVars(config, { + NAME: 'name', + SECRET: 'secret', + CERT: '-----', + KEY: '-----', + NOTE: 'note', + }), + ).toEqual({ + consumers: [ + { plugins: { 'key-auth': { key: 'secret' } }, username: 'TEST_name' }, + ], + global_rules: { '${GLOBAL_PLUGIN}': { key: 'secret' } }, + plugin_metadata: { 'file-logger': { log_format: { note: 'note' } } }, + services: [ + { + name: 'Test name', + routes: [{ name: 'Test name', uris: ['/test/name'] }], + }, + ], + ssls: [ + { + certificates: [{ certificate: '-----', key: '-----' }], + snis: ['test.com'], + }, + ], + }); + }); + }); }); diff --git a/apps/cli/src/command/utils.ts b/apps/cli/src/command/utils.ts index 7907c96..ec81ed0 100644 --- a/apps/cli/src/command/utils.ts +++ b/apps/cli/src/command/utils.ts @@ -2,6 +2,7 @@ import { BackendAPI7 } from '@api7/adc-backend-api7'; import { BackendAPISIX } from '@api7/adc-backend-apisix'; import * as ADCSDK from '@api7/adc-sdk'; import chalk from 'chalk'; +import { isObject, mapValues } from 'lodash'; import path from 'node:path'; import pluralize from 'pluralize'; @@ -269,6 +270,31 @@ export const recursiveRemoveMetadataField = (c: ADCSDK.Configuration) => { }); }; +export const recursiveReplaceEnvVars = ( + c: ADCSDK.Configuration, + dataSource = process.env, +): ADCSDK.Configuration => { + const envVarRegex = /\$\{(\w+)\}/g; + const replaceValue = (value: unknown): unknown => { + if (typeof value === 'string') + return value.replace( + envVarRegex, + (_, envVar) => dataSource?.[envVar] || '', + ); + + return value; + }; + + const recurseReplace = (value: unknown): unknown => + isObject(value) && !Array.isArray(value) + ? mapValues(value, recurseReplace) + : Array.isArray(value) + ? value.map(recurseReplace) + : replaceValue(value); + + return mapValues(c, recurseReplace) as ADCSDK.Configuration; +}; + export const configurePluralize = () => { pluralize.addIrregularRule('route', 'routes'); pluralize.addIrregularRule('service', 'services');