2021-07-07 10:46:50 +02:00
|
|
|
import { IUnleashStores } from '../types/stores';
|
|
|
|
import { IUnleashConfig } from '../types/option';
|
|
|
|
import { Logger } from '../logger';
|
2022-11-11 11:24:56 +01:00
|
|
|
import { IEnvironment, IProjectEnvironment, ISortOrder } from '../types/model';
|
2021-07-07 10:46:50 +02:00
|
|
|
import { UNIQUE_CONSTRAINT_VIOLATION } from '../error/db-error';
|
|
|
|
import NameExistsError from '../error/name-exists-error';
|
2021-09-13 15:57:38 +02:00
|
|
|
import { sortOrderSchema } from './state-schema';
|
2021-07-07 10:46:50 +02:00
|
|
|
import NotFoundError from '../error/notfound-error';
|
2021-08-12 15:04:37 +02:00
|
|
|
import { IEnvironmentStore } from '../types/stores/environment-store';
|
|
|
|
import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store';
|
|
|
|
import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store';
|
2021-11-04 21:09:52 +01:00
|
|
|
import { IProjectStore } from 'lib/types/stores/project-store';
|
|
|
|
import MinimumOneEnvironmentError from '../error/minimum-one-environment-error';
|
2022-11-21 10:37:16 +01:00
|
|
|
import { IFlagResolver } from 'lib/types/experimental';
|
2021-07-07 10:46:50 +02:00
|
|
|
|
|
|
|
export default class EnvironmentService {
|
|
|
|
private logger: Logger;
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private environmentStore: IEnvironmentStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private featureStrategiesStore: IFeatureStrategiesStore;
|
|
|
|
|
2021-11-04 21:09:52 +01:00
|
|
|
private projectStore: IProjectStore;
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
private featureEnvironmentStore: IFeatureEnvironmentStore;
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2022-11-21 10:37:16 +01:00
|
|
|
private flagResolver: IFlagResolver;
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
constructor(
|
|
|
|
{
|
|
|
|
environmentStore,
|
|
|
|
featureStrategiesStore,
|
2021-08-12 15:04:37 +02:00
|
|
|
featureEnvironmentStore,
|
2021-11-04 21:09:52 +01:00
|
|
|
projectStore,
|
2021-08-12 15:04:37 +02:00
|
|
|
}: Pick<
|
|
|
|
IUnleashStores,
|
|
|
|
| 'environmentStore'
|
|
|
|
| 'featureStrategiesStore'
|
|
|
|
| 'featureEnvironmentStore'
|
2021-11-04 21:09:52 +01:00
|
|
|
| 'projectStore'
|
2021-08-12 15:04:37 +02:00
|
|
|
>,
|
2022-11-21 10:37:16 +01:00
|
|
|
{
|
|
|
|
getLogger,
|
|
|
|
flagResolver,
|
|
|
|
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
2021-07-07 10:46:50 +02:00
|
|
|
) {
|
|
|
|
this.logger = getLogger('services/environment-service.ts');
|
|
|
|
this.environmentStore = environmentStore;
|
|
|
|
this.featureStrategiesStore = featureStrategiesStore;
|
2021-08-12 15:04:37 +02:00
|
|
|
this.featureEnvironmentStore = featureEnvironmentStore;
|
2021-11-04 21:09:52 +01:00
|
|
|
this.projectStore = projectStore;
|
2022-11-21 10:37:16 +01:00
|
|
|
this.flagResolver = flagResolver;
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async getAll(): Promise<IEnvironment[]> {
|
2022-11-11 11:24:56 +01:00
|
|
|
return this.environmentStore.getAllWithCounts();
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async get(name: string): Promise<IEnvironment> {
|
2021-08-12 15:04:37 +02:00
|
|
|
return this.environmentStore.get(name);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2022-11-11 11:24:56 +01:00
|
|
|
async getProjectEnvironments(
|
|
|
|
projectId: string,
|
|
|
|
): Promise<IProjectEnvironment[]> {
|
|
|
|
return this.environmentStore.getProjectEnvironments(projectId);
|
|
|
|
}
|
|
|
|
|
2021-09-13 15:57:38 +02:00
|
|
|
async updateSortOrder(sortOrder: ISortOrder): Promise<void> {
|
|
|
|
await sortOrderSchema.validateAsync(sortOrder);
|
|
|
|
await Promise.all(
|
|
|
|
Object.keys(sortOrder).map((key) => {
|
|
|
|
const value = sortOrder[key];
|
|
|
|
return this.environmentStore.updateSortOrder(key, value);
|
|
|
|
}),
|
|
|
|
);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
|
2021-09-13 15:57:38 +02:00
|
|
|
async toggleEnvironment(name: string, value: boolean): Promise<void> {
|
2021-07-07 10:46:50 +02:00
|
|
|
const exists = await this.environmentStore.exists(name);
|
|
|
|
if (exists) {
|
2021-09-13 15:57:38 +02:00
|
|
|
return this.environmentStore.updateProperty(name, 'enabled', value);
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
throw new NotFoundError(`Could not find environment ${name}`);
|
|
|
|
}
|
|
|
|
|
2021-09-13 10:23:57 +02:00
|
|
|
async addEnvironmentToProject(
|
2021-07-07 10:46:50 +02:00
|
|
|
environment: string,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
try {
|
2021-09-13 10:23:57 +02:00
|
|
|
await this.featureEnvironmentStore.connectProject(
|
|
|
|
environment,
|
|
|
|
projectId,
|
|
|
|
);
|
|
|
|
await this.featureEnvironmentStore.connectFeatures(
|
|
|
|
environment,
|
|
|
|
projectId,
|
|
|
|
);
|
2021-07-07 10:46:50 +02:00
|
|
|
} catch (e) {
|
|
|
|
if (e.code === UNIQUE_CONSTRAINT_VIOLATION) {
|
|
|
|
throw new NameExistsError(
|
|
|
|
`${projectId} already has the environment ${environment} enabled`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-11 10:16:58 +01:00
|
|
|
async overrideEnabledProjects(
|
2022-03-11 14:52:13 +01:00
|
|
|
environmentNamesToEnable: string[],
|
2022-03-11 10:16:58 +01:00
|
|
|
): Promise<void> {
|
2022-03-11 14:52:13 +01:00
|
|
|
if (environmentNamesToEnable.length === 0) {
|
2022-03-11 10:16:58 +01:00
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2022-03-11 14:52:13 +01:00
|
|
|
const allEnvironments = await this.environmentStore.getAll();
|
|
|
|
const existingEnvironmentsToEnable = allEnvironments.filter((env) =>
|
|
|
|
environmentNamesToEnable.includes(env.name),
|
2022-03-11 10:16:58 +01:00
|
|
|
);
|
2022-03-11 14:52:13 +01:00
|
|
|
|
|
|
|
if (
|
|
|
|
existingEnvironmentsToEnable.length !==
|
|
|
|
environmentNamesToEnable.length
|
|
|
|
) {
|
|
|
|
this.logger.warn(
|
2022-03-11 10:16:58 +01:00
|
|
|
"Found environment enabled overrides but some of the specified environments don't exist, no overrides will be executed",
|
|
|
|
);
|
2022-03-11 14:52:13 +01:00
|
|
|
return Promise.resolve();
|
2022-03-11 10:16:58 +01:00
|
|
|
}
|
2022-03-11 14:52:13 +01:00
|
|
|
|
|
|
|
const environmentsNotAlreadyEnabled =
|
|
|
|
existingEnvironmentsToEnable.filter((env) => env.enabled == false);
|
|
|
|
const environmentsToDisable = allEnvironments.filter((env) => {
|
|
|
|
return (
|
|
|
|
!environmentNamesToEnable.includes(env.name) &&
|
|
|
|
env.enabled == true
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
await this.environmentStore.disable(environmentsToDisable);
|
|
|
|
await this.environmentStore.enable(environmentsNotAlreadyEnabled);
|
|
|
|
|
|
|
|
await this.remapProjectsLinks(
|
|
|
|
environmentsToDisable,
|
|
|
|
environmentsNotAlreadyEnabled,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private async remapProjectsLinks(
|
|
|
|
toDisable: IEnvironment[],
|
|
|
|
toEnable: IEnvironment[],
|
|
|
|
) {
|
|
|
|
const projectLinks =
|
|
|
|
await this.projectStore.getProjectLinksForEnvironments(
|
|
|
|
toDisable.map((env) => env.name),
|
|
|
|
);
|
|
|
|
|
|
|
|
const unlinkTasks = projectLinks.map((link) => {
|
|
|
|
return this.forceRemoveEnvironmentFromProject(
|
|
|
|
link.environmentName,
|
|
|
|
link.projectId,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
await Promise.all(unlinkTasks.flat());
|
|
|
|
|
|
|
|
const uniqueProjects = [
|
|
|
|
...new Set(projectLinks.map((link) => link.projectId)),
|
|
|
|
];
|
|
|
|
|
|
|
|
let linkTasks = uniqueProjects.map((project) => {
|
|
|
|
return toEnable.map((enabledEnv) => {
|
|
|
|
return this.addEnvironmentToProject(enabledEnv.name, project);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await Promise.all(linkTasks.flat());
|
|
|
|
}
|
|
|
|
|
|
|
|
async forceRemoveEnvironmentFromProject(
|
|
|
|
environment: string,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
|
|
|
await this.featureEnvironmentStore.disconnectFeatures(
|
|
|
|
environment,
|
|
|
|
projectId,
|
|
|
|
);
|
|
|
|
await this.featureEnvironmentStore.disconnectProject(
|
|
|
|
environment,
|
|
|
|
projectId,
|
|
|
|
);
|
2022-03-11 10:16:58 +01:00
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async removeEnvironmentFromProject(
|
|
|
|
environment: string,
|
|
|
|
projectId: string,
|
|
|
|
): Promise<void> {
|
2021-11-04 21:09:52 +01:00
|
|
|
const projectEnvs = await this.projectStore.getEnvironmentsForProject(
|
2021-07-07 10:46:50 +02:00
|
|
|
projectId,
|
|
|
|
);
|
2021-11-04 21:09:52 +01:00
|
|
|
|
|
|
|
if (projectEnvs.length > 1) {
|
2022-03-11 14:52:13 +01:00
|
|
|
await this.forceRemoveEnvironmentFromProject(
|
2021-11-04 21:09:52 +01:00
|
|
|
environment,
|
|
|
|
projectId,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throw new MinimumOneEnvironmentError(
|
|
|
|
'You must always have one active environment',
|
2021-07-07 10:46:50 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|