1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

add support for cloning environments (#2205)

* Add support for cloning environments

Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
Simon Hornby 2022-10-28 11:27:11 +02:00 committed by GitHub
parent 28880adc2d
commit 0553976240
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 177 additions and 0 deletions

View File

@ -435,4 +435,17 @@ export class AccessStore implements IAccessStore {
})
.delete();
}
async cloneEnvironmentPermissions(
sourceEnvironment: string,
destinationEnvironment: string,
): Promise<void> {
return this.db.raw(
`insert into role_permission
(role_id, permission_id, environment)
(select role_id, permission_id, ?
from ${T.ROLE_PERMISSION} where environment = ?)`,
[destinationEnvironment, sourceEnvironment],
);
}
}

View File

@ -9,10 +9,12 @@ import metricsHelper from '../util/metrics-helper';
import { DB_TIME } from '../metric-events';
import { IFeatureEnvironment } from '../types/model';
import NotFoundError from '../error/notfound-error';
import { v4 as uuidv4 } from 'uuid';
const T = {
featureEnvs: 'feature_environments',
featureStrategies: 'feature_strategies',
features: 'features',
};
interface IFeatureEnvironmentRow {
@ -21,6 +23,11 @@ interface IFeatureEnvironmentRow {
enabled: boolean;
}
interface ISegmentRow {
id: string;
segment_id: number;
}
export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
private db: Knex;
@ -268,4 +275,83 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
}),
);
}
async copyEnvironmentFeaturesByProjects(
sourceEnvironment: string,
destinationEnvironment: string,
projects: string[],
): Promise<void> {
await this.db.raw(
`INSERT INTO ${T.featureEnvs} (
SELECT distinct ? AS environment, feature_name, enabled FROM ${T.featureEnvs} INNER JOIN ${T.features} ON ${T.featureEnvs}.feature_name = ${T.features}.name WHERE environment = ? AND project = ANY(?))`,
[destinationEnvironment, sourceEnvironment, projects],
);
}
async cloneStrategies(
sourceEnvironment: string,
destinationEnvironment: string,
): Promise<void> {
let sourceFeatureStrategies = await this.db('feature_strategies').where(
{
environment: sourceEnvironment,
},
);
const clonedStrategyRows = sourceFeatureStrategies.map(
(featureStrategy) => {
return {
id: uuidv4(),
feature_name: featureStrategy.feature_name,
project_name: featureStrategy.project_name,
environment: destinationEnvironment,
strategy_name: featureStrategy.strategy_name,
parameters: JSON.stringify(featureStrategy.parameters),
constraints: JSON.stringify(featureStrategy.constraints),
sort_order: featureStrategy.sort_order,
};
},
);
if (clonedStrategyRows.length === 0) {
return Promise.resolve();
}
await this.db('feature_strategies').insert(clonedStrategyRows);
const newStrategyMapping = new Map();
sourceFeatureStrategies.forEach((sourceStrategy, index) => {
newStrategyMapping.set(
sourceStrategy.id,
clonedStrategyRows[index].id,
);
});
const segmentsToClone: ISegmentRow[] = await this.db(
'feature_strategy_segment as fss',
)
.select(['id', 'segment_id'])
.join(
'feature_strategies AS fs',
'fss.feature_strategy_id',
'fs.id',
)
.where('environment', sourceEnvironment);
const clonedSegmentIdRows = segmentsToClone.map(
(existingSegmentRow) => {
return {
feature_strategy_id: newStrategyMapping.get(
existingSegmentRow.id,
),
segment_id: existingSegmentRow.segment_id,
};
},
);
if (clonedSegmentIdRows.length > 0) {
await this.db('feature_strategy_segment').insert(
clonedSegmentIdRows,
);
}
}
}

View File

@ -246,6 +246,23 @@ class ProjectStore implements IProjectStore {
.ignore();
}
async addEnvironmentToProjects(
environment: string,
projects: string[],
): Promise<void> {
const rows = projects.map((project) => {
return {
project_id: project,
environment_name: environment,
};
});
await this.db('project_environments')
.insert(rows)
.onConflict(['project_id', 'environment_name'])
.ignore();
}
async getEnvironmentsForProject(id: string): Promise<string[]> {
return this.db('project_environments')
.where({

View File

@ -131,6 +131,13 @@ export interface IEnvironmentCreate {
enabled?: boolean;
}
export interface IEnvironmentClone {
name: string;
projects?: string[];
type: string;
clonePermissions?: boolean;
}
export interface IEnvironmentOverview {
name: string;
enabled: boolean;

View File

@ -127,4 +127,9 @@ export interface IAccessStore extends Store<IRole, number> {
permission: string,
environment?: string,
): Promise<void>;
cloneEnvironmentPermissions(
sourceEnvironment: string,
destinationEnvironment: string,
): Promise<void>;
}

View File

@ -48,4 +48,13 @@ export interface IFeatureEnvironmentStore
connectProject(environment: string, projectId: string): Promise<void>;
disconnectProject(environment: string, projectId: string): Promise<void>;
copyEnvironmentFeaturesByProjects(
sourceEnvironment: string,
destinationEnvironment: string,
projects: string[],
): Promise<void>;
cloneStrategies(
sourceEnvironment: string,
destinationEnvironment: string,
): Promise<void>;
}

View File

@ -47,4 +47,8 @@ export interface IProjectStore extends Store<IProject, string> {
getProjectLinksForEnvironments(
environments: string[],
): Promise<IEnvironmentProjectLink[]>;
addEnvironmentToProjects(
environment: string,
projects: string[],
): Promise<void>;
}

View File

@ -185,6 +185,13 @@ class AccessStoreMock implements IAccessStore {
removeRolesOfTypeForUser(userId: number, roleType: string): Promise<void> {
return Promise.resolve(undefined);
}
cloneEnvironmentPermissions(
sourceEnvironment: string,
destinationEnvironment: string,
): Promise<void> {
return Promise.resolve(undefined);
}
}
module.exports = AccessStoreMock;

View File

@ -161,4 +161,24 @@ export default class FakeFeatureEnvironmentStore
): Promise<void> {
return Promise.reject(new Error('Not implemented'));
}
copyEnvironmentFeaturesByProjects(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
sourceEnvironment: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
destinationEnvironment: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
projects: string[],
): Promise<void> {
throw new Error('Method not implemented.');
}
cloneStrategies(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
sourceEnvironment: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
destinationEnvironment: string,
): Promise<void> {
throw new Error('Method not implemented.');
}
}

View File

@ -141,4 +141,13 @@ export default class FakeProjectStore implements IProjectStore {
getProjectsByUser(userId: number): Promise<string[]> {
throw new Error('Method not implemented.');
}
addEnvironmentToProjects(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
environment: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
projects: string[],
): Promise<void> {
throw new Error('Method not implemented.');
}
}