mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
* fix: Does not delete api_tokens on drop-Import * feat: Cleans unused apiTokens on environment import * refactor: Moves ALL_PROJECTS and ALL_ENVIRONMENTS to constants * refactor: Renames migration 20220528143630 for a more precise name * refactor: Removes unecessary console.log * fix: Adds correct down-script for migration 20220528143630
This commit is contained in:
parent
d7c450abf8
commit
7ead887147
19
scripts/docker-compose.yml
Normal file
19
scripts/docker-compose.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
version: '3.1'
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:alpine3.15
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: unleash_user
|
||||||
|
POSTGRES_PASSWORD: passord
|
||||||
|
POSTGRES_DB: unleash
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
pgadmin:
|
||||||
|
image: dpage/pgadmin4:6.8
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: 'admin@admin.com'
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: 'admin'
|
||||||
|
ports:
|
||||||
|
- 8080:80
|
@ -11,7 +11,7 @@ import {
|
|||||||
IApiTokenCreate,
|
IApiTokenCreate,
|
||||||
isAllProjects,
|
isAllProjects,
|
||||||
} from '../types/models/api-token';
|
} from '../types/models/api-token';
|
||||||
import { ALL_PROJECTS } from '../../lib/services/access-service';
|
import { ALL_PROJECTS } from '../util/constants';
|
||||||
|
|
||||||
const TABLE = 'api_tokens';
|
const TABLE = 'api_tokens';
|
||||||
const API_LINK_TABLE = 'api_token_project';
|
const API_LINK_TABLE = 'api_token_project';
|
||||||
|
@ -24,13 +24,10 @@ import NameExistsError from '../error/name-exists-error';
|
|||||||
import { IEnvironmentStore } from 'lib/types/stores/environment-store';
|
import { IEnvironmentStore } from 'lib/types/stores/environment-store';
|
||||||
import RoleInUseError from '../error/role-in-use-error';
|
import RoleInUseError from '../error/role-in-use-error';
|
||||||
import { roleSchema } from '../schema/role-schema';
|
import { roleSchema } from '../schema/role-schema';
|
||||||
import { CUSTOM_ROLE_TYPE } from '../util/constants';
|
import { CUSTOM_ROLE_TYPE, ALL_PROJECTS, ALL_ENVS } from '../util/constants';
|
||||||
import { DEFAULT_PROJECT } from '../types/project';
|
import { DEFAULT_PROJECT } from '../types/project';
|
||||||
import InvalidOperationError from '../error/invalid-operation-error';
|
import InvalidOperationError from '../error/invalid-operation-error';
|
||||||
|
|
||||||
export const ALL_PROJECTS = '*';
|
|
||||||
export const ALL_ENVS = '*';
|
|
||||||
|
|
||||||
const { ADMIN } = permissions;
|
const { ADMIN } = permissions;
|
||||||
|
|
||||||
const PROJECT_ADMIN = [
|
const PROJECT_ADMIN = [
|
||||||
|
@ -46,10 +46,11 @@ import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-stor
|
|||||||
import { IEnvironmentStore } from '../types/stores/environment-store';
|
import { IEnvironmentStore } from '../types/stores/environment-store';
|
||||||
import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store';
|
import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store';
|
||||||
import { IUnleashStores } from '../types/stores';
|
import { IUnleashStores } from '../types/stores';
|
||||||
import { DEFAULT_ENV } from '../util/constants';
|
import { DEFAULT_ENV, ALL_ENVS } from '../util/constants';
|
||||||
import { GLOBAL_ENV } from '../types/environment';
|
import { GLOBAL_ENV } from '../types/environment';
|
||||||
import { ISegmentStore } from '../types/stores/segment-store';
|
import { ISegmentStore } from '../types/stores/segment-store';
|
||||||
import { PartialSome } from '../types/partial';
|
import { PartialSome } from '../types/partial';
|
||||||
|
import { IApiTokenStore } from 'lib/types/stores/api-token-store';
|
||||||
|
|
||||||
export interface IBackupOption {
|
export interface IBackupOption {
|
||||||
includeFeatureToggles: boolean;
|
includeFeatureToggles: boolean;
|
||||||
@ -92,6 +93,8 @@ export default class StateService {
|
|||||||
|
|
||||||
private segmentStore: ISegmentStore;
|
private segmentStore: ISegmentStore;
|
||||||
|
|
||||||
|
private apiTokenStore: IApiTokenStore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
stores: IUnleashStores,
|
stores: IUnleashStores,
|
||||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
||||||
@ -107,6 +110,7 @@ export default class StateService {
|
|||||||
this.featureTagStore = stores.featureTagStore;
|
this.featureTagStore = stores.featureTagStore;
|
||||||
this.environmentStore = stores.environmentStore;
|
this.environmentStore = stores.environmentStore;
|
||||||
this.segmentStore = stores.segmentStore;
|
this.segmentStore = stores.segmentStore;
|
||||||
|
this.apiTokenStore = stores.apiTokenStore;
|
||||||
this.logger = getLogger('services/state-service.js');
|
this.logger = getLogger('services/state-service.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,6 +437,15 @@ export default class StateService {
|
|||||||
data: env,
|
data: env,
|
||||||
}));
|
}));
|
||||||
await this.eventStore.batchStore(importedEnvironmentEvents);
|
await this.eventStore.batchStore(importedEnvironmentEvents);
|
||||||
|
|
||||||
|
const apiTokens = await this.apiTokenStore.getAll();
|
||||||
|
const envNames = importedEnvs.map((env) => env.name);
|
||||||
|
apiTokens
|
||||||
|
.filter((apiToken) => !(apiToken.environment === ALL_ENVS))
|
||||||
|
.filter((apiToken) => !envNames.includes(apiToken.environment))
|
||||||
|
.forEach((apiToken) =>
|
||||||
|
this.apiTokenStore.delete(apiToken.secret),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
export const DEFAULT_ENV = 'default';
|
export const DEFAULT_ENV = 'default';
|
||||||
|
|
||||||
|
export const ALL_PROJECTS = '*';
|
||||||
|
export const ALL_ENVS = '*';
|
||||||
|
|
||||||
export const ROOT_PERMISSION_TYPE = 'root';
|
export const ROOT_PERMISSION_TYPE = 'root';
|
||||||
export const ENVIRONMENT_PERMISSION_TYPE = 'environment';
|
export const ENVIRONMENT_PERMISSION_TYPE = 'environment';
|
||||||
export const PROJECT_PERMISSION_TYPE = 'project';
|
export const PROJECT_PERMISSION_TYPE = 'project';
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.up = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE api_tokens DROP CONSTRAINT api_tokens_environment_fkey;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE api_tokens ADD CONSTRAINT api_tokens_environment_fkey FOREIGN KEY(environment) REFERENCES environments(name) ON DELETE CASCADE;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
@ -3,6 +3,7 @@ import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
|||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||||
import { collectIds } from '../../../../lib/util/collect-ids';
|
import { collectIds } from '../../../../lib/util/collect-ids';
|
||||||
|
import { ApiTokenType } from '../../../../lib/types/models/api-token';
|
||||||
|
|
||||||
const importData = require('../../../examples/import.json');
|
const importData = require('../../../examples/import.json');
|
||||||
|
|
||||||
@ -337,3 +338,105 @@ test(`should import segments and connect them to feature strategies`, async () =
|
|||||||
expect(activeSegments.length).toEqual(1);
|
expect(activeSegments.length).toEqual(1);
|
||||||
expect(collectIds(activeSegments)).toEqual([1]);
|
expect(collectIds(activeSegments)).toEqual([1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test(`should not delete api_tokens on import when drop-flag is set`, async () => {
|
||||||
|
const projectId = 'reimported-project';
|
||||||
|
const environment = 'reimported-environment';
|
||||||
|
const apiTokenName = 'not-dropped-token';
|
||||||
|
const featureName = 'reimportedFeature';
|
||||||
|
const userName = 'apiTokens-user';
|
||||||
|
|
||||||
|
await db.stores.environmentStore.create({
|
||||||
|
name: environment,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
await db.stores.projectStore.create({
|
||||||
|
name: projectId,
|
||||||
|
id: projectId,
|
||||||
|
description: 'Project for export',
|
||||||
|
});
|
||||||
|
await app.services.environmentService.addEnvironmentToProject(
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
);
|
||||||
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
|
projectId,
|
||||||
|
{
|
||||||
|
type: 'Release',
|
||||||
|
name: featureName,
|
||||||
|
description: 'Feature for export',
|
||||||
|
},
|
||||||
|
userName,
|
||||||
|
);
|
||||||
|
await app.services.featureToggleServiceV2.createStrategy(
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
constraints: [
|
||||||
|
{ contextName: 'userId', operator: 'IN', values: ['123'] },
|
||||||
|
],
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
featureName,
|
||||||
|
environment,
|
||||||
|
},
|
||||||
|
userName,
|
||||||
|
);
|
||||||
|
await app.services.apiTokenService.createApiTokenWithProjects({
|
||||||
|
username: apiTokenName,
|
||||||
|
type: ApiTokenType.CLIENT,
|
||||||
|
environment: environment,
|
||||||
|
projects: [projectId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await app.services.stateService.export({});
|
||||||
|
await app.services.stateService.import({
|
||||||
|
data,
|
||||||
|
dropBeforeImport: true,
|
||||||
|
keepExisting: false,
|
||||||
|
userName: userName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiTokens = await app.services.apiTokenService.getAllTokens();
|
||||||
|
|
||||||
|
expect(apiTokens.length).toEqual(1);
|
||||||
|
expect(apiTokens[0].username).toBe(apiTokenName);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should clean apitokens for not existing environment after import with drop`, async () => {
|
||||||
|
const projectId = 'not-reimported-project';
|
||||||
|
const environment = 'not-reimported-environment';
|
||||||
|
const apiTokenName = 'dropped-token';
|
||||||
|
|
||||||
|
await db.stores.environmentStore.create({
|
||||||
|
name: environment,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
await db.stores.projectStore.create({
|
||||||
|
name: projectId,
|
||||||
|
id: projectId,
|
||||||
|
description: 'Project for export',
|
||||||
|
});
|
||||||
|
await app.services.environmentService.addEnvironmentToProject(
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
);
|
||||||
|
await app.services.apiTokenService.createApiTokenWithProjects({
|
||||||
|
username: apiTokenName,
|
||||||
|
type: ApiTokenType.CLIENT,
|
||||||
|
environment: environment,
|
||||||
|
projects: [projectId],
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/state/import?drop=true')
|
||||||
|
.attach('file', 'src/test/examples/v3-minimal.json')
|
||||||
|
.expect(202);
|
||||||
|
|
||||||
|
const apiTokens = await app.services.apiTokenService.getAllTokens();
|
||||||
|
|
||||||
|
console.log(apiTokens);
|
||||||
|
|
||||||
|
expect(apiTokens.length).toEqual(0);
|
||||||
|
});
|
||||||
|
@ -2,10 +2,7 @@ import dbInit, { ITestDb } from '../helpers/database-init';
|
|||||||
import getLogger from '../../fixtures/no-logger';
|
import getLogger from '../../fixtures/no-logger';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import {
|
import { AccessService } from '../../../lib/services/access-service';
|
||||||
AccessService,
|
|
||||||
ALL_PROJECTS,
|
|
||||||
} from '../../../lib/services/access-service';
|
|
||||||
|
|
||||||
import * as permissions from '../../../lib/types/permissions';
|
import * as permissions from '../../../lib/types/permissions';
|
||||||
import { RoleName } from '../../../lib/types/model';
|
import { RoleName } from '../../../lib/types/model';
|
||||||
@ -14,6 +11,7 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service';
|
|||||||
import ProjectService from '../../../lib/services/project-service';
|
import ProjectService from '../../../lib/services/project-service';
|
||||||
import { createTestConfig } from '../../config/test-config';
|
import { createTestConfig } from '../../config/test-config';
|
||||||
import { DEFAULT_PROJECT } from '../../../lib/types/project';
|
import { DEFAULT_PROJECT } from '../../../lib/types/project';
|
||||||
|
import { ALL_PROJECTS } from '../../../lib/util/constants';
|
||||||
import { SegmentService } from '../../../lib/services/segment-service';
|
import { SegmentService } from '../../../lib/services/segment-service';
|
||||||
|
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
|
36
src/test/examples/v3-minimal.json
Normal file
36
src/test/examples/v3-minimal.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"id": "default",
|
||||||
|
"name": "Default",
|
||||||
|
"description": "Default project",
|
||||||
|
"createdAt": "2022-05-19T18:28:31.927Z",
|
||||||
|
"health": 100,
|
||||||
|
"updatedAt": "2022-05-19T20:28:32.736Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"type": "production",
|
||||||
|
"sortOrder": 1,
|
||||||
|
"enabled": false,
|
||||||
|
"protected": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "development",
|
||||||
|
"type": "development",
|
||||||
|
"sortOrder": 100,
|
||||||
|
"enabled": true,
|
||||||
|
"protected": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "production",
|
||||||
|
"type": "production",
|
||||||
|
"sortOrder": 200,
|
||||||
|
"enabled": true,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user