feat: 为 pipeline 添加运行环境,并用于配置文件的发布。

This commit is contained in:
Ivan Li 2021-10-20 22:48:08 +08:00
parent 3ba8fc9759
commit 6b9f846154
22 changed files with 64 additions and 45 deletions

1
.eslintcache Normal file

File diff suppressed because one or more lines are too long

6
package-lock.json generated
View File

@ -76,7 +76,7 @@
}
},
"../configuration": {
"version": "1.0.0",
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"debug": "^4.3.2",
@ -90,7 +90,7 @@
"@types/node": "^14.17.17",
"rimraf": "^3.0.2",
"ts-node": "^10.2.1",
"typescript": "^4.4.3"
"typescript": "^4.4.4"
}
},
"node_modules/@angular-devkit/core": {
@ -23187,7 +23187,7 @@
"js-yaml": "^4.1.0",
"rimraf": "^3.0.2",
"ts-node": "^10.2.1",
"typescript": "^4.4.3"
"typescript": "^4.4.4"
}
},
"consola": {

View File

@ -65,7 +65,7 @@ import { ConfigurationsModule } from './configurations/configurations.module';
playground: true,
autoSchemaFile: true,
installSubscriptionHandlers: true,
context: ({ req, connection, ...args }) => {
context: ({ req, connection }) => {
return connection ? { req: connection.context } : { req };
},
subscriptions: {

View File

@ -13,9 +13,9 @@ export class ApplicationException extends Error {
this.error = message.error;
this.message = message.message as any;
} else if (typeof message === 'string') {
super((message as unknown) as any);
super(message as unknown as any);
} else {
super((message as unknown) as any);
super(message as unknown as any);
}
}

View File

@ -19,12 +19,16 @@ export class HttpExceptionFilter implements ExceptionFilter {
case 'graphql': {
const errorName = exception.message;
const extensions: Record<string, any> = {};
const err = exception.getResponse();
const err = exception.getResponse() as any;
if (typeof err === 'string') {
extensions.message = err;
} else {
Object.assign(extensions, (err as any).extension);
extensions.message = (err as any).message;
Object.assign(extensions, err.extension);
if (typeof err.message === 'string') {
extensions.message = err.message;
} else {
extensions.message = err.error;
}
}
extensions.error = errorName;
this.logger.error(extensions);

View File

@ -126,6 +126,7 @@ export class BaseDbService<Entity extends AppBaseEntity> extends TypeormHelper {
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async canYouRemoveWithIds(ids: string[]): Promise<void> {
return;
}

View File

@ -1,3 +1,4 @@
import { JwtService } from '@nestjs-lib/auth';
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigurationsResolver } from './configurations.resolver';
import { ConfigurationsService } from './configurations.service';
@ -8,10 +9,15 @@ describe('ConfigurationsResolver', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ConfigurationsResolver,
{
provide: ConfigurationsService,
useValue: {},
},
{
provide: JwtService,
useValue: {},
},
],
}).compile();

View File

@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { ConfigurationsService } from './configurations.service';
import { Configuration } from './entities/configuration.entity';
import { IsNull } from 'typeorm';
import { Etcd3 } from 'etcd3';
describe('ConfigurationsService', () => {
let service: ConfigurationsService;
@ -15,6 +16,10 @@ describe('ConfigurationsService', () => {
provide: getRepositoryToken(Configuration),
useValue: {},
},
{
provide: Etcd3,
useValue: {},
},
],
}).compile();
@ -28,7 +33,7 @@ describe('ConfigurationsService', () => {
describe('findOneByConditions', () => {
it('should select by projectId only', async () => {
const entity = new Configuration();
const findOne = jest.fn((_) => Promise.resolve(entity));
const findOne = jest.fn<any, [any]>(() => Promise.resolve(entity));
service['repository'].findOne = findOne;
await expect(

View File

@ -26,7 +26,7 @@ export class ConfigurationsService extends BaseDbService<Configuration> {
entity = this.repository.create(dto);
}
entity = await this.repository.save(entity);
this.etcd.put(`share/config/${entity.id}`).value(entity.content);
await this.syncToEtcd(entity);
return entity;
}
@ -36,4 +36,20 @@ export class ConfigurationsService extends BaseDbService<Configuration> {
}
return await this.repository.findOne(dto);
}
async syncToEtcd({ pipelineId, id }: { pipelineId?: string; id?: string }) {
const config = await this.repository.findOneOrFail({
where: { pipelineId, id },
relations: ['pipeline', 'project'],
});
await this.etcd
.put(`share/config/${config.id}`)
.value(config.content)
.exec();
await this.etcd
.put(`share/config/${config.pipeline.environment}/${config.project.name}`)
.value(config.content)
.exec();
}
}

View File

@ -1,4 +1,4 @@
import { Field, InputType } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { PipelineUnits } from '../enums/pipeline-units.enum';
@InputType()

View File

@ -1,6 +1,6 @@
import { Field, InputType, Int, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { IsInstance, isInstance, ValidateNested } from 'class-validator';
import { IsInstance, ValidateNested } from 'class-validator';
import { WorkUnit } from './work-unit.model';
@InputType('WorkUnitMetadataInput')

View File

@ -35,15 +35,6 @@ export class PipelineTasksResolver {
);
}
@Subscription(() => PipelineTask, {
resolve: (value) => {
return value;
},
})
async pipelineTaskChanged(@Args('id') id: string) {
// return await this.service.watchTaskUpdated(id);
}
@Query(() => [PipelineTask])
async listPipelineTaskByPipelineId(@Args('pipelineId') pipelineId: string) {
return await this.service.listTasksByPipelineId(pipelineId);

View File

@ -12,7 +12,6 @@ describe('PipelineTasksService', () => {
let service: PipelineTasksService;
let module: TestingModule;
let taskRepository: Repository<PipelineTask>;
let pipelineRepository: Repository<Pipeline>;
beforeEach(async () => {
module = await Test.createTestingModule({
@ -43,7 +42,6 @@ describe('PipelineTasksService', () => {
service = module.get<PipelineTasksService>(PipelineTasksService);
taskRepository = module.get(getRepositoryToken(PipelineTask));
pipelineRepository = module.get(getRepositoryToken(Pipeline));
jest
.spyOn(taskRepository, 'save')
.mockImplementation(async (data: any) => data);

View File

@ -4,7 +4,6 @@ import { PipelineTask } from './pipeline-task.entity';
import { Repository } from 'typeorm';
import { CreatePipelineTaskInput } from './dtos/create-pipeline-task.input';
import { Pipeline } from '../pipelines/pipeline.entity';
import debug from 'debug';
import { AmqpConnection, RabbitRPC } from '@golevelup/nestjs-rabbitmq';
import {
EXCHANGE_PIPELINE_TASK_TOPIC,
@ -19,8 +18,6 @@ import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
import { getAppInstanceRouteKey } from '../commons/utils/rabbit-mq';
import { ROUTE_PIPELINE_TASK_KILL } from './pipeline-tasks.constants';
const log = debug('fennec:pipeline-tasks:service');
@Injectable()
export class PipelineTasksService {
constructor(

View File

@ -28,4 +28,8 @@ export class CreatePipelineInput {
@ValidateNested()
@IsInstance(WorkUnitMetadata)
workUnitMetadata: WorkUnitMetadata;
@IsString()
@MaxLength(100)
environment: string;
}

View File

@ -12,7 +12,7 @@ export class Pipeline extends AppBaseEntity {
@Column()
projectId: string;
@Column({ comment: 'eg: remotes/origin/master' })
@Column({ comment: 'E.g., remotes/origin/master' })
branch: string;
@Column()
@ -20,4 +20,7 @@ export class Pipeline extends AppBaseEntity {
@Column({ type: 'jsonb' })
workUnitMetadata: WorkUnitMetadata;
@Column()
environment: string;
}

View File

@ -2,13 +2,11 @@ import { Test, TestingModule } from '@nestjs/testing';
import { PipelinesService } from './pipelines.service';
import { Pipeline } from './pipeline.entity';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Project } from '../projects/project.entity';
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
describe('PipelinesService', () => {
let service: PipelinesService;
let repository: Repository<Pipeline>;
let pipeline: Pipeline;
beforeEach(async () => {
@ -40,7 +38,6 @@ describe('PipelinesService', () => {
}).compile();
service = module.get<PipelinesService>(PipelinesService);
repository = module.get(getRepositoryToken(Pipeline));
});
it('should be defined', () => {

View File

@ -19,7 +19,9 @@ import { plainToClass } from 'class-transformer';
@Injectable()
export class PipelinesService extends BaseDbService<Pipeline> {
readonly uniqueFields: Array<Array<keyof Pipeline>> = [['projectId', 'name']];
readonly uniqueFields: Array<Array<keyof Pipeline>> = [
['projectId', 'name', 'environment'],
];
constructor(
@InjectRepository(Pipeline)
readonly repository: Repository<Pipeline>,

View File

@ -1,5 +1,5 @@
import { ObjectType } from '@nestjs/graphql';
import { Entity, Column, DeleteDateColumn } from 'typeorm';
import { Entity, Column } from 'typeorm';
import { AppBaseEntity } from '../commons/entities/app-base-entity';
@ObjectType()

View File

@ -1,10 +1,5 @@
import { ObjectType, Field } from '@nestjs/graphql';
import {
LogResult,
DefaultLogFields,
BranchSummary,
BranchSummaryBranch,
} from 'simple-git';
import { BranchSummaryBranch } from 'simple-git';
@ObjectType()
export class Branch implements BranchSummaryBranch {

View File

@ -161,7 +161,7 @@ describe('ReposService', () => {
const project = new Project();
const pipeline = new Pipeline();
pipeline.branch = 'test';
const fetch = jest.fn((_: any) => Promise.resolve());
const fetch = jest.fn<any, [any]>(() => Promise.resolve());
pipeline.project = project;
const getGit = jest.spyOn(service, 'getGit').mockImplementation(() =>
Promise.resolve({
@ -182,7 +182,7 @@ describe('ReposService', () => {
const project = new Project();
const pipeline = new Pipeline();
pipeline.branch = 'test';
const fetch = jest.fn((_: any) => Promise.resolve());
const fetch = jest.fn<any, [any]>(() => Promise.resolve());
pipeline.project = project;
const getGit = jest
.spyOn(service, 'getGit')
@ -196,7 +196,7 @@ describe('ReposService', () => {
const project = new Project();
const pipeline = new Pipeline();
pipeline.branch = 'test';
const fetch = jest.fn((_: any) => Promise.reject('error'));
const fetch = jest.fn<any, [any]>(() => Promise.reject('error'));
pipeline.project = project;
const getGit = jest.spyOn(service, 'getGit').mockImplementation(() =>
Promise.resolve({

View File

@ -1,4 +1,4 @@
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PipelineTasksModule } from '../pipeline-tasks/pipeline-tasks.module';
import { GiteaWebhooksController } from './gitea-webhooks.controller';
@ -10,5 +10,4 @@ import { WebhooksService } from './webhooks.service';
controllers: [GiteaWebhooksController],
providers: [WebhooksService],
})
export class WebhooksModule {
}
export class WebhooksModule {}