init
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { Injectable, NotFoundException, Logger, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as k8s from '@kubernetes/client-node';
|
||||
import { Cluster } from './entities/cluster.entity';
|
||||
import { CreateClusterDto, UpdateClusterDto } from './dto/cluster.dto';
|
||||
import { ClusterStatus } from '../common/enums';
|
||||
|
||||
@Injectable()
|
||||
export class ClustersService {
|
||||
private readonly logger = new Logger(ClustersService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Cluster)
|
||||
private clustersRepository: Repository<Cluster>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Test connection to a Kubernetes cluster using its kubeconfig.
|
||||
* Calls the /version endpoint to verify the cluster is reachable.
|
||||
*/
|
||||
async testConnection(kubeconfig: string): Promise<{ connected: boolean; version?: string; error?: string }> {
|
||||
try {
|
||||
const kc = new k8s.KubeConfig();
|
||||
kc.loadFromString(kubeconfig);
|
||||
|
||||
const versionApi = kc.makeApiClient(k8s.VersionApi);
|
||||
const result = await versionApi.getCode();
|
||||
const info = result.body;
|
||||
|
||||
this.logger.log(`Cluster connection OK: Kubernetes ${info.gitVersion}`);
|
||||
return {
|
||||
connected: true,
|
||||
version: info.gitVersion,
|
||||
};
|
||||
} catch (err: any) {
|
||||
const message = err?.body?.message || err?.message || 'Unknown connection error';
|
||||
this.logger.warn(`Cluster connection failed: ${message}`);
|
||||
return {
|
||||
connected: false,
|
||||
error: message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async create(dto: CreateClusterDto): Promise<Cluster> {
|
||||
// Validate kubeconfig by testing actual connection
|
||||
const connectionTest = await this.testConnection(dto.kubeconfig);
|
||||
if (!connectionTest.connected) {
|
||||
throw new BadRequestException(
|
||||
`Cannot connect to Kubernetes cluster: ${connectionTest.error}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (dto.isDefault === true) {
|
||||
const existingDefaults = await this.clustersRepository.find({ where: { isDefault: true } });
|
||||
for (const c of existingDefaults) {
|
||||
c.isDefault = false;
|
||||
await this.clustersRepository.save(c);
|
||||
}
|
||||
}
|
||||
|
||||
const cluster = this.clustersRepository.create({
|
||||
...dto,
|
||||
status: ClusterStatus.ACTIVE, // Connection verified — mark active
|
||||
});
|
||||
const saved = await this.clustersRepository.save(cluster);
|
||||
this.logger.log(`Cluster "${saved.name}" registered (active) — K8s ${connectionTest.version}`);
|
||||
return saved;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Cluster[]> {
|
||||
return this.clustersRepository.find({
|
||||
select: ['id', 'name', 'description', 'status', 'apiServer', 'region', 'provider', 'isDefault', 'createdAt'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<Cluster> {
|
||||
const cluster = await this.clustersRepository.findOne({ where: { id } });
|
||||
if (!cluster) {
|
||||
throw new NotFoundException('Cluster not found');
|
||||
}
|
||||
return cluster;
|
||||
}
|
||||
|
||||
async getDefault(): Promise<Cluster> {
|
||||
const cluster = await this.clustersRepository.findOne({ where: { isDefault: true } });
|
||||
if (!cluster) {
|
||||
throw new NotFoundException('No default cluster configured');
|
||||
}
|
||||
return cluster;
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateClusterDto): Promise<Cluster> {
|
||||
const cluster = await this.findOne(id);
|
||||
|
||||
// If kubeconfig is being updated, re-test connection
|
||||
if (dto.kubeconfig) {
|
||||
const connectionTest = await this.testConnection(dto.kubeconfig);
|
||||
if (!connectionTest.connected) {
|
||||
throw new BadRequestException(
|
||||
`Cannot connect to Kubernetes cluster: ${connectionTest.error}`,
|
||||
);
|
||||
}
|
||||
dto.status = ClusterStatus.ACTIVE;
|
||||
this.logger.log(`Cluster "${cluster.name}" kubeconfig updated — connection verified (K8s ${connectionTest.version})`);
|
||||
}
|
||||
|
||||
if (dto.isDefault === true) {
|
||||
const existingDefaults = await this.clustersRepository.find({ where: { isDefault: true } });
|
||||
for (const c of existingDefaults) {
|
||||
if (c.id !== id) {
|
||||
c.isDefault = false;
|
||||
await this.clustersRepository.save(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.assign(cluster, dto);
|
||||
return this.clustersRepository.save(cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually test connectivity to an existing cluster.
|
||||
* Updates status to active/inactive based on result.
|
||||
*/
|
||||
async testClusterById(id: string): Promise<{ connected: boolean; version?: string; error?: string }> {
|
||||
const cluster = await this.findOne(id);
|
||||
const result = await this.testConnection(cluster.kubeconfig);
|
||||
|
||||
cluster.status = result.connected ? ClusterStatus.ACTIVE : ClusterStatus.INACTIVE;
|
||||
await this.clustersRepository.save(cluster);
|
||||
this.logger.log(`Cluster "${cluster.name}" test: ${result.connected ? 'ACTIVE' : 'INACTIVE'}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
const cluster = await this.findOne(id);
|
||||
await this.clustersRepository.remove(cluster);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user