Skip to content

Commit

Permalink
feat: Add environment variable to set override enabled environments
Browse files Browse the repository at this point in the history
  • Loading branch information
sighphyre committed Mar 11, 2022
1 parent 3910e23 commit c3b064a
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/lib/create-config.test.ts
Expand Up @@ -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([]);
});
11 changes: 11 additions & 0 deletions src/lib/create-config.ts
Expand Up @@ -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 = {};

Expand Down Expand Up @@ -275,6 +283,8 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
{ initApiTokens: initApiTokens },
]);

const environmentEnableOverrides = loadEnvironmentEnableOverrides();

const importSetting: IImportOption = mergeAll([
defaultImport,
options.import,
Expand Down Expand Up @@ -323,6 +333,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
eventHook: options.eventHook,
enterpriseVersion: options.enterpriseVersion,
eventBus: new EventEmitter(),
environmentEnableOverrides,
};
}

Expand Down
16 changes: 16 additions & 0 deletions src/lib/db/environment-store.ts
Expand Up @@ -163,6 +163,22 @@ export default class EnvironmentStore implements IEnvironmentStore {
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> {
await this.db(TABLE).where({ name, protected: false }).del();
}
Expand Down
6 changes: 6 additions & 0 deletions src/lib/server-impl.ts
Expand Up @@ -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(
Expand Down
22 changes: 22 additions & 0 deletions src/lib/services/environment-service.ts
Expand Up @@ -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(
environment: string,
projectId: string,
Expand Down
1 change: 1 addition & 0 deletions src/lib/types/option.ts
Expand Up @@ -158,4 +158,5 @@ export interface IUnleashConfig {
enterpriseVersion?: string;
eventBus: EventEmitter;
disableLegacyFeaturesApi?: boolean;
environmentEnableOverrides?: string[];
}
2 changes: 2 additions & 0 deletions src/lib/types/stores/environment-store.ts
Expand Up @@ -16,4 +16,6 @@ export interface IEnvironmentStore extends Store<IEnvironment, string> {
updateSortOrder(id: string, value: number): Promise<void>;
importEnvironments(environments: IEnvironment[]): Promise<IEnvironment[]>;
delete(name: string): Promise<void>;
disableAllExcept(environments: string[]): Promise<void>;
enable(environments: string[]): Promise<void>;
}
53 changes: 53 additions & 0 deletions src/test/e2e/services/environment-service.test.ts
Expand Up @@ -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);
});
14 changes: 14 additions & 0 deletions src/test/fixtures/fake-environment-store.ts
Expand Up @@ -10,6 +10,20 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {

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[]> {
return this.environments;
}
Expand Down

0 comments on commit c3b064a

Please sign in to comment.