From 9d33054f033f4ac119ea521ac60ef3fa2578d245 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Wed, 9 Aug 2023 12:49:52 +0200 Subject: [PATCH] fix(core): update --- test/test.ts | 4 +- ts/00_commitinfo_data.ts | 2 +- ts/qenv.classes.qenv.ts | 243 +++++++++++++++------------------------ 3 files changed, 94 insertions(+), 155 deletions(-) diff --git a/test/test.ts b/test/test.ts index 026caeb..429879e 100644 --- a/test/test.ts +++ b/test/test.ts @@ -18,12 +18,12 @@ tap.test('should create a new class', async () => { }); tap.test('key1 should be not be overwritten since it is already present', async () => { - expect(testQenv.getEnvVarRequired('key1')).toEqual('original'); + expect(testQenv.getEnvVarOnDemand('key1')).toEqual('original'); expect(testQenv.getEnvVarOnDemand('key1')).toEqual('original'); }); tap.test('key2 should be read from Yml', async () => { - expect(testQenv.getEnvVarRequired('key2')).toEqual('fromJson'); + expect(testQenv.getEnvVarOnDemand('key2')).toEqual('fromJson'); expect(testQenv.getEnvVarOnDemand('key2')).toEqual('fromJson'); }); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index de3e7ac..a77ff84 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/qenv', - version: '5.0.4', + version: '5.0.5', description: 'easy promised environments' } diff --git a/ts/qenv.classes.qenv.ts b/ts/qenv.classes.qenv.ts index 06a2b88..d0d881c 100644 --- a/ts/qenv.classes.qenv.ts +++ b/ts/qenv.classes.qenv.ts @@ -1,9 +1,5 @@ import * as plugins from './qenv.plugins.js'; -/** - * class Qenv - * allows to make assertions about the environments while being more flexibel in how to meet them - */ export class Qenv { public requiredEnvVars: string[] = []; public availableEnvVars: string[] = []; @@ -11,34 +7,54 @@ export class Qenv { public keyValueObject: { [key: string]: any } = {}; public logger = new plugins.smartlog.ConsoleLog(); - // filePaths public qenvFilePathAbsolute: string; public envFilePathAbsolute: string; - constructor(qenvFileBasePathArg = process.cwd(), envFileBasePathArg, failOnMissing = true) { - // lets make sure paths are absolute - this.qenvFilePathAbsolute = plugins.path.join( - plugins.path.resolve(qenvFileBasePathArg), - 'qenv.yml' - ); - this.envFilePathAbsolute = plugins.path.join( - plugins.path.resolve(envFileBasePathArg), - 'env.json' - ); + constructor( + qenvFileBasePathArg: string = process.cwd(), + envFileBasePathArg: string, + failOnMissing: boolean = true + ) { + this.initializeFilePaths(qenvFileBasePathArg, envFileBasePathArg); + this.loadRequiredEnvVars(); + this.loadAvailableEnvVars(); + this.checkForMissingEnvVars(failOnMissing); + } - this.getRequiredEnvVars(); - this.getAvailableEnvVars(); + private initializeFilePaths(qenvFileBasePathArg: string, envFileBasePathArg: string) { + this.qenvFilePathAbsolute = plugins.path.join(plugins.path.resolve(qenvFileBasePathArg), 'qenv.yml'); + this.envFilePathAbsolute = plugins.path.join(plugins.path.resolve(envFileBasePathArg), 'env.json'); + } - this.missingEnvVars = this.getMissingEnvVars(); + private loadRequiredEnvVars() { + if (plugins.smartfile.fs.fileExistsSync(this.qenvFilePathAbsolute)) { + const qenvFile = plugins.smartfile.fs.toObjectSync(this.qenvFilePathAbsolute); + if (qenvFile?.required && Array.isArray(qenvFile.required)) { + this.requiredEnvVars.push(...qenvFile.required); + } else { + this.logger.log('warn', 'qenv.yml does not contain a "required" Array!'); + } + } + } + + private loadAvailableEnvVars() { + for (const envVar of this.requiredEnvVars) { + const value = this.getEnvVarOnDemand(envVar); + if (value) { + this.availableEnvVars.push(envVar); + this.keyValueObject[envVar] = value; + } + } + } + + private checkForMissingEnvVars(failOnMissing: boolean) { + this.missingEnvVars = this.requiredEnvVars.filter((envVar) => !this.availableEnvVars.includes(envVar)); - // handle missing variables if (this.missingEnvVars.length > 0) { - console.info('Required Env Vars are:'); - console.log(this.requiredEnvVars); - console.error('However some Env variables could not be resolved:'); - console.log(this.missingEnvVars); + console.info('Required Env Vars are:', this.requiredEnvVars); + console.error('Missing Env Vars:', this.missingEnvVars); if (failOnMissing) { - this.logger.log('error', 'Exiting!'); + this.logger.log('error', 'Exiting due to missing env vars!'); process.exit(1); } else { this.logger.log('warn', 'qenv is not set to fail on missing environment variables'); @@ -46,150 +62,73 @@ export class Qenv { } } - /** - * only gets an environment variable if it is required within a read qenv.yml file - * @param envVarName - */ - public getEnvVarRequired(envVarName): string { - return this.keyValueObject[envVarName]; + public getEnvVarOnDemand(envVarName: string): string | undefined { + return ( + this.getFromEnvironmentVariable(envVarName) || + this.getFromEnvJsonFile(envVarName) || + this.getFromDockerSecret(envVarName) || + this.getFromDockerSecretJson(envVarName) + ); } - /** - * tries to get any env var even if it is not required - * @param wantedEnvVar - */ - public getEnvVarOnDemand(wantedEnvVar: string): string { - let envVarFromEnvironmentVariable: string; - let envVarFromEnvJsonFile: string; - let envVarFromDockerSecret: string; - let dockerSecretJson: string; - - // env var check - if (process.env[wantedEnvVar]) { - this.availableEnvVars.push(wantedEnvVar); - envVarFromEnvironmentVariable = process.env[wantedEnvVar]; + public getEnvVarOnDemandAsObject(envVarName: string): any { + const rawValue = this.getEnvVarOnDemand(envVarName); + if (rawValue && rawValue.startsWith('base64Object:')) { + const base64Part = rawValue.split('base64Object:')[1]; + return this.decodeBase64(base64Part); } + return rawValue; + } - // env file check - // lets determine the actual env yml - let envJsonFileAsObject; + private getFromEnvironmentVariable(envVarName: string): string | undefined { + return process.env[envVarName]; + } + + private getFromEnvJsonFile(envVarName: string): string | undefined { try { - envJsonFileAsObject = plugins.smartfile.fs.toObjectSync(this.envFilePathAbsolute); - } catch (err) { - envJsonFileAsObject = {}; - } - if (envJsonFileAsObject.hasOwnProperty(wantedEnvVar)) { - envVarFromEnvJsonFile = envJsonFileAsObject[wantedEnvVar]; + const envJson = plugins.smartfile.fs.toObjectSync(this.envFilePathAbsolute); + const value = envJson[envVarName]; + if (typeof value === 'object') { + return 'base64Object:' + this.encodeBase64(value); + } + return value; + } catch (error) { + return undefined; } + } - // docker secret check - if ( - plugins.smartfile.fs.isDirectory('/run') && - plugins.smartfile.fs.isDirectory('/run/secrets') && - plugins.smartfile.fs.fileExistsSync(`/run/secrets/${wantedEnvVar}`) - ) { - envVarFromDockerSecret = plugins.smartfile.fs.toStringSync(`/run/secrets/${wantedEnvVar}`); + private getFromDockerSecret(envVarName: string): string | undefined { + const secretPath = `/run/secrets/${envVarName}`; + if (plugins.smartfile.fs.fileExistsSync(secretPath)) { + return plugins.smartfile.fs.toStringSync(secretPath); } + return undefined; + } - // docker secret.json - if ( - plugins.smartfile.fs.isDirectory('/run') && - plugins.smartfile.fs.isDirectory('/run/secrets') - ) { + private getFromDockerSecretJson(envVarName: string): string | undefined { + if (plugins.smartfile.fs.isDirectory('/run/secrets')) { const availableSecrets = plugins.smartfile.fs.listAllItemsSync('/run/secrets'); for (const secret of availableSecrets) { - if (secret.includes('secret.json') && !envVarFromDockerSecret) { + if (secret.includes('secret.json')) { const secretObject = plugins.smartfile.fs.toObjectSync(`/run/secrets/${secret}`); - envVarFromDockerSecret = secretObject[wantedEnvVar]; + const value = secretObject[envVarName]; + if (typeof value === 'object') { + return 'base64Object:' + this.encodeBase64(value); + } + return value; } } } - - // warn if there is more than one candidate - const availableCcandidates: any[] = []; - [ - envVarFromEnvironmentVariable, - envVarFromEnvJsonFile, - envVarFromDockerSecret, - dockerSecretJson, - ].forEach((candidate) => { - if (candidate) { - availableCcandidates.push(candidate); - } - }); - if (availableCcandidates.length > 1) { - this.logger.log( - 'warn', - `found multiple candidates for ${wantedEnvVar} Choosing in the order of envVar, envFileVar, dockerSecret, dockerSecretJson` - ); - console.log(availableCcandidates); - } - - switch (true) { - case !!envVarFromEnvironmentVariable: - this.logger.log('ok', `found ${wantedEnvVar} as environment variable`); - return envVarFromEnvironmentVariable; - case !!envVarFromEnvJsonFile: - this.logger.log('ok', `found ${wantedEnvVar} as env.json variable`); - return envVarFromEnvJsonFile; - case !!envVarFromDockerSecret: - this.logger.log('ok', `found ${wantedEnvVar} as docker secret`); - return envVarFromDockerSecret; - case !!dockerSecretJson: - this.logger.log('ok', `found ${wantedEnvVar} as docker secret.json`); - return dockerSecretJson; - default: - this.logger.log( - 'warn', - `could not find the wanted environment variable ${wantedEnvVar} anywhere` - ); - return; - } + return undefined; } - /** - * gets the required env values - */ - private getRequiredEnvVars = () => { - let qenvFile: any = {}; - if (plugins.smartfile.fs.fileExistsSync(this.qenvFilePathAbsolute)) { - qenvFile = plugins.smartfile.fs.toObjectSync(this.qenvFilePathAbsolute); - } - if (!qenvFile || !qenvFile.required || !Array.isArray(qenvFile.required)) { - this.logger.log( - 'warn', - `qenv (promised environment): ./qenv.yml File does not contain a 'required' Array! This might be ok though.` - ); - } else { - for (const keyArg of Object.keys(qenvFile.required)) { - this.requiredEnvVars.push(qenvFile.required[keyArg]); - } - } - }; + private encodeBase64(data: any): string { + const jsonString = JSON.stringify(data); + return Buffer.from(jsonString).toString('base64'); + } - /** - * gets the available env vars - */ - private getAvailableEnvVars = () => { - for (const requiredEnvVar of this.requiredEnvVars) { - const chosenVar = this.getEnvVarOnDemand(requiredEnvVar); - if (chosenVar) { - this.availableEnvVars.push(requiredEnvVar); - this.keyValueObject[requiredEnvVar] = chosenVar; - } - } - }; - - /** - * gets missing env vars - */ - private getMissingEnvVars = (): string[] => { - const missingEnvVars: string[] = []; - for (const envVar of this.requiredEnvVars) { - if (!this.availableEnvVars.includes(envVar)) { - missingEnvVars.push(envVar); - } - } - return missingEnvVars; - }; + private decodeBase64(encodedString: string): any { + const decodedString = Buffer.from(encodedString, 'base64').toString('utf-8'); + return JSON.parse(decodedString); + } }