feat: add configuration-center module.
This commit is contained in:
parent
d5f49531e9
commit
3ba8fc9759
@ -20,6 +20,7 @@ import { LoggerModule } from 'nestjs-pino';
|
||||
import { EtcdModule } from 'nestjs-etcd';
|
||||
import pinoPretty from 'pino-pretty';
|
||||
import { fromPairs, map, pipe, toPairs } from 'ramda';
|
||||
import { ConfigurationsModule } from './configurations/configurations.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -106,6 +107,7 @@ import { fromPairs, map, pipe, toPairs } from 'ramda';
|
||||
}),
|
||||
WebhooksModule,
|
||||
CommonsModule,
|
||||
ConfigurationsModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, AppResolver],
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { PasswordConverter } from './services/password-converter';
|
||||
import { RedisMutexModule } from './redis-mutex/redis-mutex.module';
|
||||
import { AuthModule } from '@nestjs-lib/auth';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [RedisMutexModule, AuthModule],
|
||||
providers: [PasswordConverter],
|
||||
|
11
src/configurations/configurations.module.ts
Normal file
11
src/configurations/configurations.module.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Configuration } from './entities/configuration.entity';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigurationsService } from './configurations.service';
|
||||
import { ConfigurationsResolver } from './configurations.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Configuration])],
|
||||
providers: [ConfigurationsResolver, ConfigurationsService],
|
||||
})
|
||||
export class ConfigurationsModule {}
|
24
src/configurations/configurations.resolver.spec.ts
Normal file
24
src/configurations/configurations.resolver.spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigurationsResolver } from './configurations.resolver';
|
||||
import { ConfigurationsService } from './configurations.service';
|
||||
|
||||
describe('ConfigurationsResolver', () => {
|
||||
let resolver: ConfigurationsResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: ConfigurationsService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<ConfigurationsResolver>(ConfigurationsResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
39
src/configurations/configurations.resolver.ts
Normal file
39
src/configurations/configurations.resolver.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import { GetConfigurationArgs } from './dto/get-configuration.args';
|
||||
import { SetConfigurationInput } from './dto/set-configuration.input';
|
||||
import { Resolver, Mutation, Args, Query } from '@nestjs/graphql';
|
||||
import { ConfigurationsService } from './configurations.service';
|
||||
import { Configuration } from './entities/configuration.entity';
|
||||
import { any, pipe, values } from 'ramda';
|
||||
import { AccountRole, Roles } from '@nestjs-lib/auth';
|
||||
|
||||
@Roles(AccountRole.admin, AccountRole.super)
|
||||
@Resolver(() => Configuration)
|
||||
export class ConfigurationsResolver {
|
||||
constructor(private readonly configurationsService: ConfigurationsService) {}
|
||||
|
||||
@Mutation(() => Configuration)
|
||||
setConfiguration(
|
||||
@Args('setConfigurationInput', { type: () => SetConfigurationInput })
|
||||
setConfigurationInput: SetConfigurationInput,
|
||||
) {
|
||||
return this.configurationsService.setConfiguration(setConfigurationInput);
|
||||
}
|
||||
|
||||
@Query(() => Configuration, { nullable: true })
|
||||
getConfiguration(
|
||||
@Args()
|
||||
getConfigurationArgs: GetConfigurationArgs,
|
||||
) {
|
||||
if (
|
||||
pipe(
|
||||
values,
|
||||
any((value) => !value),
|
||||
)(getConfigurationArgs)
|
||||
) {
|
||||
throw new UnprocessableEntityException('Must pass a parameter');
|
||||
}
|
||||
|
||||
return this.configurationsService.findOneByConditions(getConfigurationArgs);
|
||||
}
|
||||
}
|
44
src/configurations/configurations.service.spec.ts
Normal file
44
src/configurations/configurations.service.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigurationsService } from './configurations.service';
|
||||
import { Configuration } from './entities/configuration.entity';
|
||||
import { IsNull } from 'typeorm';
|
||||
|
||||
describe('ConfigurationsService', () => {
|
||||
let service: ConfigurationsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ConfigurationsService,
|
||||
{
|
||||
provide: getRepositoryToken(Configuration),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ConfigurationsService>(ConfigurationsService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('findOneByConditions', () => {
|
||||
it('should select by projectId only', async () => {
|
||||
const entity = new Configuration();
|
||||
const findOne = jest.fn((_) => Promise.resolve(entity));
|
||||
service['repository'].findOne = findOne;
|
||||
|
||||
await expect(
|
||||
service.findOneByConditions({ projectId: 'uuid' }),
|
||||
).resolves.toEqual(entity);
|
||||
|
||||
expect(findOne.mock.calls[0][0]).toMatchObject({
|
||||
projectId: 'uuid',
|
||||
pipelineId: IsNull(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
39
src/configurations/configurations.service.ts
Normal file
39
src/configurations/configurations.service.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { GetConfigurationArgs } from './dto/get-configuration.args';
|
||||
import { BaseDbService } from './../commons/services/base-db.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Configuration } from './entities/configuration.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { FindConditions, IsNull, Repository } from 'typeorm';
|
||||
import { SetConfigurationInput } from './dto/set-configuration.input';
|
||||
import { pick } from 'ramda';
|
||||
import { Etcd3 } from 'etcd3';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigurationsService extends BaseDbService<Configuration> {
|
||||
constructor(
|
||||
@InjectRepository(Configuration)
|
||||
configurationRepository: Repository<Configuration>,
|
||||
private readonly etcd: Etcd3,
|
||||
) {
|
||||
super(configurationRepository);
|
||||
}
|
||||
|
||||
async setConfiguration(dto: SetConfigurationInput) {
|
||||
let entity = await this.repository.findOne(
|
||||
pick(['pipelineId', 'projectId'], dto),
|
||||
);
|
||||
if (!entity) {
|
||||
entity = this.repository.create(dto);
|
||||
}
|
||||
entity = await this.repository.save(entity);
|
||||
this.etcd.put(`share/config/${entity.id}`).value(entity.content);
|
||||
return entity;
|
||||
}
|
||||
|
||||
async findOneByConditions(dto: FindConditions<GetConfigurationArgs>) {
|
||||
if (dto.projectId && !dto.pipelineId) {
|
||||
dto.pipelineId = IsNull();
|
||||
}
|
||||
return await this.repository.findOne(dto);
|
||||
}
|
||||
}
|
17
src/configurations/dto/get-configuration.args.ts
Normal file
17
src/configurations/dto/get-configuration.args.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { ArgsType } from '@nestjs/graphql';
|
||||
import { IsUUID, IsOptional } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class GetConfigurationArgs {
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
pipelineId?: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
projectId?: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
id?: string;
|
||||
}
|
26
src/configurations/dto/set-configuration.input.ts
Normal file
26
src/configurations/dto/set-configuration.input.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ConfigurationLanguage } from './../enums/configuration-language.enum';
|
||||
import { IsEnum, IsString, IsUUID, Length, IsOptional } from 'class-validator';
|
||||
import { InputType } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class SetConfigurationInput {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
id?: string;
|
||||
|
||||
@IsUUID()
|
||||
pipelineId: string;
|
||||
|
||||
@IsUUID()
|
||||
projectId: string;
|
||||
|
||||
@IsString()
|
||||
content: string;
|
||||
|
||||
@IsEnum(ConfigurationLanguage)
|
||||
language: ConfigurationLanguage;
|
||||
|
||||
@Length(0, 100)
|
||||
@IsOptional()
|
||||
name = 'Default Configuration';
|
||||
}
|
35
src/configurations/entities/configuration.entity.ts
Normal file
35
src/configurations/entities/configuration.entity.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Project } from './../../projects/project.entity';
|
||||
import { ConfigurationLanguage } from './../enums/configuration-language.enum';
|
||||
import { Pipeline } from './../../pipelines/pipeline.entity';
|
||||
import { AppBaseEntity } from './../../commons/entities/app-base-entity';
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
import { Column, Entity, ManyToOne } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@ObjectType()
|
||||
export class Configuration extends AppBaseEntity {
|
||||
@ManyToOne(() => Pipeline)
|
||||
pipeline: Pipeline;
|
||||
|
||||
@Column({ unique: true, nullable: true })
|
||||
pipelineId: string;
|
||||
|
||||
@ManyToOne(() => Project)
|
||||
project: Project;
|
||||
|
||||
@Column()
|
||||
projectId: string;
|
||||
|
||||
@Column({ comment: 'language defined in type field.' })
|
||||
content: string;
|
||||
|
||||
@Column({ comment: '配置名称' })
|
||||
name: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ConfigurationLanguage,
|
||||
comment: 'configuration content language',
|
||||
})
|
||||
language: ConfigurationLanguage;
|
||||
}
|
10
src/configurations/enums/configuration-language.enum.ts
Normal file
10
src/configurations/enums/configuration-language.enum.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum ConfigurationLanguage {
|
||||
JavaScript = 'JavaScript',
|
||||
YAML = 'YAML',
|
||||
}
|
||||
|
||||
registerEnumType(ConfigurationLanguage, {
|
||||
name: 'ConfigurationLanguage',
|
||||
});
|
@ -16,12 +16,10 @@ import {
|
||||
} from './pipeline-tasks.constants';
|
||||
import { PipelineTaskLogger } from './pipeline-task.logger';
|
||||
import { PipelineTaskFlushService } from './pipeline-task-flush.service';
|
||||
import { CommonsModule } from '../commons/commons.module';
|
||||
import { DeployByPm2Service } from './runners/deploy-by-pm2/deploy-by-pm2.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CommonsModule,
|
||||
TypeOrmModule.forFeature([PipelineTask, Pipeline]),
|
||||
RedisModule,
|
||||
ReposModule,
|
||||
|
@ -5,14 +5,11 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Pipeline } from './pipeline.entity';
|
||||
import { CommitLogsResolver } from './commit-logs.resolver';
|
||||
import { PipelineTasksModule } from '../pipeline-tasks/pipeline-tasks.module';
|
||||
import { ReposModule } from '../repos/repos.module';
|
||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { CommonsModule } from '../commons/commons.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CommonsModule,
|
||||
TypeOrmModule.forFeature([Pipeline]),
|
||||
PipelineTasksModule,
|
||||
RabbitMQModule.forRootAsync(RabbitMQModule, {
|
||||
|
@ -6,11 +6,9 @@ import { Project } from './project.entity';
|
||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { EXCHANGE_PROJECT_FANOUT } from './projects.constants';
|
||||
import { CommonsModule } from '../commons/commons.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CommonsModule,
|
||||
TypeOrmModule.forFeature([Project]),
|
||||
RabbitMQModule.forRootAsync(RabbitMQModule, {
|
||||
imports: [ConfigModule],
|
||||
|
@ -7,14 +7,12 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { ProjectsModule } from '../projects/projects.module';
|
||||
import { EXCHANGE_REPO } from './repos.constants';
|
||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
|
||||
import { CommonsModule } from '../commons/commons.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Project]),
|
||||
ConfigModule,
|
||||
ProjectsModule,
|
||||
CommonsModule,
|
||||
RabbitMQModule.forRootAsync(RabbitMQModule, {
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
|
Loading…
Reference in New Issue
Block a user