mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Add environment variable to set override enabled environments
This commit is contained in:
		
							parent
							
								
									3910e23d2d
								
							
						
					
					
						commit
						c3b064adfc
					
				@ -224,3 +224,47 @@ test('should handle cases where no env var specified for tokens', async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    expect(config.authentication.initApiTokens).toHaveLength(1);
 | 
					    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([]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
					export function createConfig(options: IUnleashOptions): IUnleashConfig {
 | 
				
			||||||
    let extraDbOptions = {};
 | 
					    let extraDbOptions = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -275,6 +283,8 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
 | 
				
			|||||||
        { initApiTokens: initApiTokens },
 | 
					        { initApiTokens: initApiTokens },
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const environmentEnableOverrides = loadEnvironmentEnableOverrides();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const importSetting: IImportOption = mergeAll([
 | 
					    const importSetting: IImportOption = mergeAll([
 | 
				
			||||||
        defaultImport,
 | 
					        defaultImport,
 | 
				
			||||||
        options.import,
 | 
					        options.import,
 | 
				
			||||||
@ -323,6 +333,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
 | 
				
			|||||||
        eventHook: options.eventHook,
 | 
					        eventHook: options.eventHook,
 | 
				
			||||||
        enterpriseVersion: options.enterpriseVersion,
 | 
					        enterpriseVersion: options.enterpriseVersion,
 | 
				
			||||||
        eventBus: new EventEmitter(),
 | 
					        eventBus: new EventEmitter(),
 | 
				
			||||||
 | 
					        environmentEnableOverrides,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -163,6 +163,22 @@ export default class EnvironmentStore implements IEnvironmentStore {
 | 
				
			|||||||
        return mapRow(row[0]);
 | 
					        return mapRow(row[0]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async disableAllExcept(environments: string[]): Promise<void> {
 | 
				
			||||||
 | 
					        await this.db(TABLE)
 | 
				
			||||||
 | 
					            .update({
 | 
				
			||||||
 | 
					                enabled: false,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .whereNotIn('name', environments);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async enable(environments: string[]): Promise<void> {
 | 
				
			||||||
 | 
					        await this.db(TABLE)
 | 
				
			||||||
 | 
					            .update({
 | 
				
			||||||
 | 
					                enabled: true,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .whereIn('name', environments);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async delete(name: string): Promise<void> {
 | 
					    async delete(name: string): Promise<void> {
 | 
				
			||||||
        await this.db(TABLE).where({ name, protected: false }).del();
 | 
					        await this.db(TABLE).where({ name, protected: false }).del();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -87,6 +87,12 @@ async function createApp(
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.environmentEnableOverrides?.length > 0) {
 | 
				
			||||||
 | 
					        await services.environmentService.overrideEnabledProjects(
 | 
				
			||||||
 | 
					            config.environmentEnableOverrides,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
        if (startApp) {
 | 
					        if (startApp) {
 | 
				
			||||||
            const server = stoppable(
 | 
					            const server = stoppable(
 | 
				
			||||||
 | 
				
			|||||||
@ -94,6 +94,28 @@ export default class EnvironmentService {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async overrideEnabledProjects(
 | 
				
			||||||
 | 
					        environmentsToEnable: string[],
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        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(
 | 
					    async removeEnvironmentFromProject(
 | 
				
			||||||
        environment: string,
 | 
					        environment: string,
 | 
				
			||||||
        projectId: string,
 | 
					        projectId: string,
 | 
				
			||||||
 | 
				
			|||||||
@ -158,4 +158,5 @@ export interface IUnleashConfig {
 | 
				
			|||||||
    enterpriseVersion?: string;
 | 
					    enterpriseVersion?: string;
 | 
				
			||||||
    eventBus: EventEmitter;
 | 
					    eventBus: EventEmitter;
 | 
				
			||||||
    disableLegacyFeaturesApi?: boolean;
 | 
					    disableLegacyFeaturesApi?: boolean;
 | 
				
			||||||
 | 
					    environmentEnableOverrides?: string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,4 +16,6 @@ export interface IEnvironmentStore extends Store<IEnvironment, string> {
 | 
				
			|||||||
    updateSortOrder(id: string, value: number): Promise<void>;
 | 
					    updateSortOrder(id: string, value: number): Promise<void>;
 | 
				
			||||||
    importEnvironments(environments: IEnvironment[]): Promise<IEnvironment[]>;
 | 
					    importEnvironments(environments: IEnvironment[]): Promise<IEnvironment[]>;
 | 
				
			||||||
    delete(name: string): Promise<void>;
 | 
					    delete(name: string): Promise<void>;
 | 
				
			||||||
 | 
					    disableAllExcept(environments: string[]): Promise<void>;
 | 
				
			||||||
 | 
					    enable(environments: string[]): Promise<void>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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}`),
 | 
					        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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/test/fixtures/fake-environment-store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								src/test/fixtures/fake-environment-store.ts
									
									
									
									
										vendored
									
									
								
							@ -10,6 +10,20 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    environments: IEnvironment[] = [];
 | 
					    environments: IEnvironment[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disableAllExcept(environments: string[]): Promise<void> {
 | 
				
			||||||
 | 
					        for (let env of this.environments) {
 | 
				
			||||||
 | 
					            if (!environments.includes(env.name)) env.enabled = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Promise.resolve();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    enable(environments: string[]): Promise<void> {
 | 
				
			||||||
 | 
					        for (let env of this.environments) {
 | 
				
			||||||
 | 
					            if (environments.includes(env.name)) env.enabled = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Promise.resolve();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getAll(): Promise<IEnvironment[]> {
 | 
					    async getAll(): Promise<IEnvironment[]> {
 | 
				
			||||||
        return this.environments;
 | 
					        return this.environments;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user