From c3b064adfc24f72f45842e3e92bd029334907a04 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Fri, 11 Mar 2022 11:16:58 +0200 Subject: [PATCH] feat: Add environment variable to set override enabled environments --- src/lib/create-config.test.ts | 44 +++++++++++++++ src/lib/create-config.ts | 11 ++++ src/lib/db/environment-store.ts | 16 ++++++ src/lib/server-impl.ts | 6 +++ src/lib/services/environment-service.ts | 22 ++++++++ src/lib/types/option.ts | 1 + src/lib/types/stores/environment-store.ts | 2 + .../e2e/services/environment-service.test.ts | 53 +++++++++++++++++++ src/test/fixtures/fake-environment-store.ts | 14 +++++ 9 files changed, 169 insertions(+) diff --git a/src/lib/create-config.test.ts b/src/lib/create-config.test.ts index 92eba02bab..be9c0044a2 100644 --- a/src/lib/create-config.test.ts +++ b/src/lib/create-config.test.ts @@ -224,3 +224,47 @@ test('should handle cases where no env var specified for tokens', async () => { expect(config.authentication.initApiTokens).toHaveLength(1); }); + +test('should load environment overrides from env var', async () => { + process.env.ENABLED_ENVIRONMENTS = 'default,production'; + + const config = createConfig({ + db: { + host: 'localhost', + port: 4242, + user: 'unleash', + password: 'password', + database: 'unleash_db', + }, + server: { + port: 4242, + }, + authentication: { + initApiTokens: [], + }, + }); + + expect(config.environmentEnableOverrides).toHaveLength(2); + expect(config.environmentEnableOverrides).toContain('production'); + delete process.env.ENABLED_ENVIRONMENTS; +}); + +test('should yield an empty list when no environment overrides are specified', async () => { + const config = createConfig({ + db: { + host: 'localhost', + port: 4242, + user: 'unleash', + password: 'password', + database: 'unleash_db', + }, + server: { + port: 4242, + }, + authentication: { + initApiTokens: [], + }, + }); + + expect(config.environmentEnableOverrides).toStrictEqual([]); +}); diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 91c6d1223e..9ab90b57c6 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -217,6 +217,14 @@ const loadInitApiTokens = () => { ]; }; +const loadEnvironmentEnableOverrides = () => { + const environmentsString = process.env.ENABLED_ENVIRONMENTS; + if (environmentsString) { + return environmentsString.split(','); + } + return []; +}; + export function createConfig(options: IUnleashOptions): IUnleashConfig { let extraDbOptions = {}; @@ -275,6 +283,8 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { { initApiTokens: initApiTokens }, ]); + const environmentEnableOverrides = loadEnvironmentEnableOverrides(); + const importSetting: IImportOption = mergeAll([ defaultImport, options.import, @@ -323,6 +333,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { eventHook: options.eventHook, enterpriseVersion: options.enterpriseVersion, eventBus: new EventEmitter(), + environmentEnableOverrides, }; } diff --git a/src/lib/db/environment-store.ts b/src/lib/db/environment-store.ts index ca000b1e86..d118ae57b8 100644 --- a/src/lib/db/environment-store.ts +++ b/src/lib/db/environment-store.ts @@ -163,6 +163,22 @@ export default class EnvironmentStore implements IEnvironmentStore { return mapRow(row[0]); } + async disableAllExcept(environments: string[]): Promise { + await this.db(TABLE) + .update({ + enabled: false, + }) + .whereNotIn('name', environments); + } + + async enable(environments: string[]): Promise { + await this.db(TABLE) + .update({ + enabled: true, + }) + .whereIn('name', environments); + } + async delete(name: string): Promise { await this.db(TABLE).where({ name, protected: false }).del(); } diff --git a/src/lib/server-impl.ts b/src/lib/server-impl.ts index af038507a4..1c2904588f 100644 --- a/src/lib/server-impl.ts +++ b/src/lib/server-impl.ts @@ -87,6 +87,12 @@ async function createApp( }); } + if (config.environmentEnableOverrides?.length > 0) { + await services.environmentService.overrideEnabledProjects( + config.environmentEnableOverrides, + ); + } + return new Promise((resolve, reject) => { if (startApp) { const server = stoppable( diff --git a/src/lib/services/environment-service.ts b/src/lib/services/environment-service.ts index 48c9ce91da..02405a01a8 100644 --- a/src/lib/services/environment-service.ts +++ b/src/lib/services/environment-service.ts @@ -94,6 +94,28 @@ export default class EnvironmentService { } } + async overrideEnabledProjects( + environmentsToEnable: string[], + ): Promise { + if (environmentsToEnable.length === 0) { + return Promise.resolve(); + } + + const environmentsExist = await Promise.all( + environmentsToEnable.map((env) => + this.environmentStore.exists(env), + ), + ); + if (!environmentsExist.every((exists) => exists)) { + this.logger.error( + "Found environment enabled overrides but some of the specified environments don't exist, no overrides will be executed", + ); + return; + } + await this.environmentStore.disableAllExcept(environmentsToEnable); + await this.environmentStore.enable(environmentsToEnable); + } + async removeEnvironmentFromProject( environment: string, projectId: string, diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index f60413cd4a..61212860e0 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -158,4 +158,5 @@ export interface IUnleashConfig { enterpriseVersion?: string; eventBus: EventEmitter; disableLegacyFeaturesApi?: boolean; + environmentEnableOverrides?: string[]; } diff --git a/src/lib/types/stores/environment-store.ts b/src/lib/types/stores/environment-store.ts index c6fe2600f2..45e21d72ed 100644 --- a/src/lib/types/stores/environment-store.ts +++ b/src/lib/types/stores/environment-store.ts @@ -16,4 +16,6 @@ export interface IEnvironmentStore extends Store { updateSortOrder(id: string, value: number): Promise; importEnvironments(environments: IEnvironment[]): Promise; delete(name: string): Promise; + disableAllExcept(environments: string[]): Promise; + enable(environments: string[]): Promise; } diff --git a/src/test/e2e/services/environment-service.test.ts b/src/test/e2e/services/environment-service.test.ts index 8cb953b733..73823cce4b 100644 --- a/src/test/e2e/services/environment-service.test.ts +++ b/src/test/e2e/services/environment-service.test.ts @@ -136,3 +136,56 @@ test('Trying to get an environment that does not exist throws NotFoundError', as new NotFoundError(`Could not find environment with name: ${envName}`), ); }); + +test('Setting an override disables all other envs', async () => { + const enabledEnvName = 'should-get-enabled'; + const disabledEnvName = 'should-get-disabled'; + await db.stores.environmentStore.create({ + name: disabledEnvName, + type: 'production', + }); + + await db.stores.environmentStore.create({ + name: enabledEnvName, + type: 'production', + }); + + //Set these to the wrong state so we can assert that overriding them flips + await service.toggleEnvironment(disabledEnvName, true); + await service.toggleEnvironment(enabledEnvName, false); + + await service.overrideEnabledProjects([enabledEnvName]); + + const environments = await service.getAll(); + const targetedEnvironment = environments.find( + (env) => env.name == enabledEnvName, + ); + + const allOtherEnvironments = environments + .filter((x) => x.name != enabledEnvName) + .map((env) => env.enabled); + + console.log(allOtherEnvironments); + expect(targetedEnvironment.enabled).toBe(true); + expect(allOtherEnvironments.every((x) => x === false)).toBe(true); +}); + +test('Passing an empty override does nothing', async () => { + const enabledEnvName = 'should-be-enabled'; + + await db.stores.environmentStore.create({ + name: enabledEnvName, + type: 'production', + }); + + await service.toggleEnvironment(enabledEnvName, true); + + await service.overrideEnabledProjects([]); + + const environments = await service.getAll(); + const targetedEnvironment = environments.find( + (env) => env.name == enabledEnvName, + ); + + expect(targetedEnvironment.enabled).toBe(true); +}); diff --git a/src/test/fixtures/fake-environment-store.ts b/src/test/fixtures/fake-environment-store.ts index c7aaad3127..35847aba57 100644 --- a/src/test/fixtures/fake-environment-store.ts +++ b/src/test/fixtures/fake-environment-store.ts @@ -10,6 +10,20 @@ export default class FakeEnvironmentStore implements IEnvironmentStore { environments: IEnvironment[] = []; + disableAllExcept(environments: string[]): Promise { + for (let env of this.environments) { + if (!environments.includes(env.name)) env.enabled = false; + } + return Promise.resolve(); + } + + enable(environments: string[]): Promise { + for (let env of this.environments) { + if (environments.includes(env.name)) env.enabled = true; + } + return Promise.resolve(); + } + async getAll(): Promise { return this.environments; }