Skip to content

Commit

Permalink
feat(api): added kubernetes api
Browse files Browse the repository at this point in the history
  • Loading branch information
xmlking committed Nov 30, 2018
1 parent 83e9606 commit 00f520b
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 1 deletion.
5 changes: 4 additions & 1 deletion apps/api/src/app/app.module.ts
Expand Up @@ -8,6 +8,7 @@ import { UserModule } from './user';
import { AppController } from './app.controller';
import { NotificationsModule } from './notifications';
import { PushModule } from './push';
import { ExternalModule } from './external';

@Module({
imports: [
Expand All @@ -22,14 +23,16 @@ import { PushModule } from './push';
{ path: '/notifications', module: NotificationsModule },
],
},
{ path: '/external', module: ExternalModule },
]),
CoreModule,
AuthModule,
UserModule,
// AccountModule,
// ChatModule,
ExternalModule,
NotificationsModule,
PushModule,
// ChatModule,
],
controllers: [AppController],
})
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/app/external/external.module.ts
@@ -0,0 +1,11 @@
import { HttpModule, Module } from '@nestjs/common';
import { SharedModule } from '../shared';
import { KubernetesService } from './kubernetes/kubernetes.service';
import { KubernetesController } from './kubernetes/kubernetes.controller';

@Module({
imports: [SharedModule, HttpModule.register({ timeout: 5000 })],
providers: [KubernetesService],
controllers: [KubernetesController],
})
export class ExternalModule {}
1 change: 1 addition & 0 deletions apps/api/src/app/external/index.ts
@@ -0,0 +1 @@
export * from './external.module';
32 changes: 32 additions & 0 deletions apps/api/src/app/external/kubernetes/dto/labels.dto.ts
@@ -0,0 +1,32 @@
import { IsEnum } from 'class-validator';
import { ApiModelProperty } from '@nestjs/swagger';

export enum EnvironmentType {
Prod,
NonProd,
}
export enum ZoneType {
DMZ,
Core,
}

export enum DCType {
CLUSTER1,
CLUSTER2,
CLUSTER3,
CLUSTER4,
}

export class Labels {
@ApiModelProperty({ enum: ['Prod', 'NonProd'] })
@IsEnum(EnvironmentType)
env: EnvironmentType;

@ApiModelProperty({ required: false, enum: ['Core', 'DMZ'] })
@IsEnum(ZoneType)
zone?: ZoneType;

@ApiModelProperty({ required: false, enum: ['CLUSTER1', 'CLUSTER2', 'CLUSTER3', 'CLUSTER4'] })
@IsEnum(DCType)
dc?: DCType;
}
36 changes: 36 additions & 0 deletions apps/api/src/app/external/kubernetes/dto/search-project.dto.ts
@@ -0,0 +1,36 @@
import { IsArray, IsAscii, IsOptional, IsString, Length, Matches, ValidateNested } from 'class-validator';
import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger';
import { Labels } from './labels.dto';
import { Type } from 'class-transformer';

export class SearchProjectDto {
@ApiModelProperty({ type: [String] })
@IsArray()
@IsOptional()
readonly groups?: string[];

@ApiModelProperty({ type: String })
@IsString()
@IsOptional()
readonly userId?: string;

@ApiModelPropertyOptional({ type: String })
@IsAscii()
@IsOptional()
@Length(5, 100)
@Matches(/^[a-z\d-]+$/)
readonly namespace?: string;

@ApiModelPropertyOptional({ type: String, minLength: 5, maxLength: 100 })
@IsAscii()
@IsOptional()
@Length(5, 100)
@Matches(/^[a-z\d-]+$/)
readonly serviceAccountName: string;

@ApiModelPropertyOptional({ type: Labels })
@IsOptional()
@ValidateNested()
@Type(() => Labels)
readonly labels?: Labels;
}
15 changes: 15 additions & 0 deletions apps/api/src/app/external/kubernetes/kubernetes.controller.spec.ts
@@ -0,0 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing';
import { KubernetesController } from './kubernetes.controller';

describe('Kubernetes Controller', () => {
let module: TestingModule;
beforeAll(async () => {
module = await Test.createTestingModule({
controllers: [KubernetesController],
}).compile();
});
it('should be defined', () => {
const controller: KubernetesController = module.get<KubernetesController>(KubernetesController);
expect(controller).toBeDefined();
});
});
28 changes: 28 additions & 0 deletions apps/api/src/app/external/kubernetes/kubernetes.controller.ts
@@ -0,0 +1,28 @@
import { Controller, Get, HttpStatus, Logger, Param } from '@nestjs/common';
import { ApiOAuth2Auth, ApiOperation, ApiResponse, ApiUseTags } from '@nestjs/swagger';
import { KubernetesService } from './kubernetes.service';

@ApiOAuth2Auth(['read'])
@ApiUseTags('External', 'Kubernetes')
@ApiResponse({ status: HttpStatus.OK, description: 'Found' })
@ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Not Found' })
@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' })
@ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden' })
@Controller('kubernetes')
export class KubernetesController {
public readonly logger = new Logger(KubernetesController.name);

constructor(private readonly kubernetesService: KubernetesService) {}

@ApiOperation({ title: 'Get all namespaces im a cluster' })
@Get(':cluster')
getAll(@Param('cluster') cluster: string): Promise<any> {
return this.kubernetesService.listNamespaces(cluster);
}

@ApiOperation({ title: 'Find one namespace in a cluster by namespace' })
@Get(':cluster/:namespace')
findOne(@Param('cluster') cluster: string, @Param('namespace') namespace: string): Promise<any> {
return this.kubernetesService.getNamespace(cluster, namespace);
}
}
15 changes: 15 additions & 0 deletions apps/api/src/app/external/kubernetes/kubernetes.service.spec.ts
@@ -0,0 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing';
import { KubernetesService } from './kubernetes.service';

describe('KubernetesService', () => {
let service: KubernetesService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [KubernetesService],
}).compile();
service = module.get<KubernetesService>(KubernetesService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
127 changes: 127 additions & 0 deletions apps/api/src/app/external/kubernetes/kubernetes.service.ts
@@ -0,0 +1,127 @@
import {
BadRequestException,
ConflictException,
HttpException,
HttpStatus,
Injectable,
Logger,
NotFoundException,
OnModuleInit,
UnauthorizedException,
} from '@nestjs/common';
import * as Api from 'kubernetes-client';
import { ConfigService } from '../../config';
import { environment as env } from '@env-api/environment';

const Client = Api.Client1_10;
const config = Api.config;

@Injectable()
export class KubernetesService implements OnModuleInit {
private readonly logger = new Logger(KubernetesService.name);

private readonly clients = new Map(
Object.entries(env.kubernetes).map<[string, Api.Api]>(([key, value]) => [
key,
new Client({
config: {
url: value.baseUrl,
auth: {
bearer: value.token,
},
insecureSkipTlsVerify: true,
version: 'v1',
promises: true,
},
version: value.version || '1.10',
}),
]),
);

constructor(private readonly appConfig: ConfigService) {}

async onModuleInit() {
// @ts-ignore
// for (const [key, client] of this.clients.entries()) {
// try {
// await client.loadSpec();
// } catch (err) {
// console.error(`Unable to connect to ${key}`, err);
// }
// }
}

public async listNamespaces(cluster: string) {
try {
const namespaces = await this.clients.get(cluster).api.v1.namespaces.get();
return namespaces.body.items;
} catch (error) {
KubernetesService.handleError(error);
}
}

public async myNamespaces(cluster: string, token: string) {
try {
// this.client.get(cluster).setToken(token)
const namespaces = await this.clients.get(cluster).api.v1.namespaces.get();
return namespaces.items;
} catch (error) {
KubernetesService.handleError(error);
}
}

public async getNamespace(cluster: string, namespace: string) {
try {
const namespace1 = await this.clients
.get(cluster)
.api.v1.namespaces(namespace)
.get();
return namespace1.body;
} catch (error) {
KubernetesService.handleError(error);
}
}

public async myServiceAccounts(cluster: string, namespace: string) {
try {
const namespaces = await this.clients
.get(cluster)
.api.v1.namespaces(namespace)
.serviceaccounts.get();
return namespaces.body.items;
} catch (error) {
KubernetesService.handleError(error);
}
}

public async hasNamespace(cluster: string, namespace: string) {
try {
const foundNamespace = await this.clients
.get(cluster)
.api.v1.namespaces(namespace)
.get();
return !!foundNamespace;
} catch (error) {
if (error.code === 404) return false;
KubernetesService.handleError(error);
}
}

static handleError(error: Error & { code?: number; statusCode?: number }) {
const message = error.message || 'unknown error';
const statusCode = error.statusCode || error.code || HttpStatus.I_AM_A_TEAPOT;
console.log(message, statusCode);
switch (statusCode) {
case HttpStatus.CONFLICT:
throw new ConflictException(error.message);
case HttpStatus.UNAUTHORIZED:
throw new UnauthorizedException(error.message);
case HttpStatus.NOT_FOUND:
throw new NotFoundException(error.message);
case HttpStatus.BAD_REQUEST:
throw new BadRequestException(error.message);
default:
throw new HttpException(message, statusCode);
}
}
}
26 changes: 26 additions & 0 deletions apps/api/src/environments/environment.prod.ts
Expand Up @@ -52,4 +52,30 @@ export const environment = {
'BAJq-yHlSNjUqKW9iMY0hG96X9WdVwetUFDa5rQIGRPqOHKAL_fkKUe_gUTAKnn9IPAltqmlNO2OkJrjdQ_MXNg',
privateKey: process.env.VAPID_PRIVATE_KEY || 'cwh2CYK5h_B_Gobnv8Ym9x61B3qFE2nTeb9BeiZbtMI',
},
kubernetes: {
CLUSTER1: {
baseUrl: 'https://k8s-prod-ctc-aci.optum.com:16443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: process.env.CLUSTER1_SERVICE_ACCOUNT_TOKEN || 'AAAAAAAAAAAA',
},
CLUSTER2: {
baseUrl: 'https://k8s-prod-elr-aci.optum.com:16443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: process.env.CLUSTER2_SERVICE_ACCOUNT_TOKEN || 'BBBBBBBBBBBB',
},
CLUSTER3: {
baseUrl: 'https://k8s-prod-ptc-aci.optum.com:16443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: process.env.CLUSTER3_SERVICE_ACCOUNT_TOKEN || 'CCCCCCCCCCCCC',
},
CLUSTER4: {
baseUrl: 'https://10.176.22.126:6443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: process.env.CLUSTER4_SERVICE_ACCOUNT_TOKEN || 'DDDDDDDDDDDDD',
},
},
};
26 changes: 26 additions & 0 deletions apps/api/src/environments/environment.ts
Expand Up @@ -52,4 +52,30 @@ export const environment = {
publicKey: 'BAJq-yHlSNjUqKW9iMY0hG96X9WdVwetUFDa5rQIGRPqOHKAL_fkKUe_gUTAKnn9IPAltqmlNO2OkJrjdQ_MXNg',
privateKey: 'cwh2CYK5h_B_Gobnv8Ym9x61B3qFE2nTeb9BeiZbtMI',
},
kubernetes: {
CLUSTER1: {
baseUrl: 'https://k8s-prod-ctc-aci.optum.com:16443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: 'AAAAAAAAAAAA',
},
CLUSTER2: {
baseUrl: 'https://k8s-prod-elr-aci.optum.com:16443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: 'BBBBBBBBBBBB',
},
CLUSTER3: {
baseUrl: 'https://k8s-prod-ptc-aci.optum.com:16443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: 'CCCCCCCCCCCCC',
},
CLUSTER4: {
baseUrl: 'https://10.176.22.126:6443',
version: '1.10',
/* tslint:disable-next-line:max-line-length */
token: 'DDDDDDDDDDDDD',
},
},
};

0 comments on commit 00f520b

Please sign in to comment.