This commit is contained in:
@@ -1736,13 +1736,33 @@ export class AppsService {
|
|||||||
async deleteRuntimeProfile(profileId: string) {
|
async deleteRuntimeProfile(profileId: string) {
|
||||||
const profile = await this.prisma.runtimeEnvProfile.findUnique({
|
const profile = await this.prisma.runtimeEnvProfile.findUnique({
|
||||||
where: { id: profileId },
|
where: { id: profileId },
|
||||||
select: { id: true, name: true },
|
include: {
|
||||||
|
variables: {
|
||||||
|
include: {
|
||||||
|
component: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
type: true,
|
||||||
|
workloadType: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new NotFoundException('실행 설정 프로필을 찾을 수 없습니다.');
|
throw new NotFoundException('실행 설정 프로필을 찾을 수 없습니다.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const variable of profile.variables) {
|
||||||
|
await this.removeRuntimeVariableFromWorkload(
|
||||||
|
normalizeNamespace(profile.namespace),
|
||||||
|
variable.component,
|
||||||
|
variable.key,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.prisma.runtimeEnvProfile.delete({
|
await this.prisma.runtimeEnvProfile.delete({
|
||||||
where: { id: profileId },
|
where: { id: profileId },
|
||||||
});
|
});
|
||||||
@@ -1845,13 +1865,32 @@ export class AppsService {
|
|||||||
async deleteRuntimeVariable(profileId: string, variableId: string) {
|
async deleteRuntimeVariable(profileId: string, variableId: string) {
|
||||||
const variable = await this.prisma.runtimeEnvVariable.findFirst({
|
const variable = await this.prisma.runtimeEnvVariable.findFirst({
|
||||||
where: { id: variableId, profileId },
|
where: { id: variableId, profileId },
|
||||||
select: { id: true },
|
include: {
|
||||||
|
profile: {
|
||||||
|
select: {
|
||||||
|
namespace: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
type: true,
|
||||||
|
workloadType: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!variable) {
|
if (!variable) {
|
||||||
throw new NotFoundException('환경변수를 찾을 수 없습니다.');
|
throw new NotFoundException('환경변수를 찾을 수 없습니다.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.removeRuntimeVariableFromWorkload(
|
||||||
|
normalizeNamespace(variable.profile.namespace),
|
||||||
|
variable.component,
|
||||||
|
variable.key,
|
||||||
|
);
|
||||||
|
|
||||||
await this.prisma.runtimeEnvVariable.delete({
|
await this.prisma.runtimeEnvVariable.delete({
|
||||||
where: { id: variable.id },
|
where: { id: variable.id },
|
||||||
});
|
});
|
||||||
@@ -2205,6 +2244,32 @@ export class AppsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async removeRuntimeVariableFromWorkload(
|
||||||
|
namespace: string,
|
||||||
|
component: { name: string; type: string; workloadType: string | null } | null,
|
||||||
|
key: string,
|
||||||
|
) {
|
||||||
|
if (!component || component.type === 'DATABASE') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workloadType = normalizeWorkloadType(component.workloadType, component.type);
|
||||||
|
if (workloadType === 'Job') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workloadRef = `${workloadType.toLowerCase()}/${sanitizeK8sName(component.name)}`;
|
||||||
|
try {
|
||||||
|
await this.kubectl(['set', 'env', workloadRef, `${key}-`, '-n', namespace]);
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
if (/\bnot\s*found\b/i.test(message)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async kubectlEnv() {
|
private async kubectlEnv() {
|
||||||
const inClusterHost = process.env.KUBERNETES_SERVICE_HOST;
|
const inClusterHost = process.env.KUBERNETES_SERVICE_HOST;
|
||||||
if (inClusterHost) {
|
if (inClusterHost) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Delete, Get, Param } from '@nestjs/common';
|
||||||
|
|
||||||
import { TailscaleService } from './tailscale.service';
|
import { TailscaleService } from './tailscale.service';
|
||||||
|
|
||||||
@@ -11,6 +11,11 @@ export class TailscaleController {
|
|||||||
return this.tailscaleService.listNodes();
|
return this.tailscaleService.listNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Delete('nodes/:nodeId')
|
||||||
|
deleteNode(@Param('nodeId') nodeId: string) {
|
||||||
|
return this.tailscaleService.deleteNode(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
@Get('routes')
|
@Get('routes')
|
||||||
listRoutes() {
|
listRoutes() {
|
||||||
return this.tailscaleService.listRoutes();
|
return this.tailscaleService.listRoutes();
|
||||||
|
|||||||
@@ -63,6 +63,22 @@ export class TailscaleService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteNode(nodeId: string) {
|
||||||
|
const normalizedNodeId = nodeId.trim();
|
||||||
|
if (!normalizedNodeId) {
|
||||||
|
throw new BadGatewayException('삭제할 Headscale 노드 ID가 없습니다.');
|
||||||
|
}
|
||||||
|
if (!this.gatewayUrl || !this.gatewayToken) {
|
||||||
|
throw new BadGatewayException('PLATFORM_GATEWAY_URL 또는 PLATFORM_GATEWAY_TOKEN이 설정되어 있지 않습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.request<{ deleted?: boolean }>(`/api/admin/tailscale/nodes/${encodeURIComponent(normalizedNodeId)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
return { deleted: true, nodeId: normalizedNodeId };
|
||||||
|
}
|
||||||
|
|
||||||
async listRoutes() {
|
async listRoutes() {
|
||||||
if (!this.gatewayUrl || !this.gatewayToken) {
|
if (!this.gatewayUrl || !this.gatewayToken) {
|
||||||
return {
|
return {
|
||||||
@@ -118,11 +134,13 @@ export class TailscaleService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async request<T>(path: string): Promise<T> {
|
private async request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||||
const response = await fetch(`${this.gatewayUrl}${path}`, {
|
const response = await fetch(`${this.gatewayUrl}${path}`, {
|
||||||
|
...init,
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'x-platform-gateway-token': this.gatewayToken ?? '',
|
'x-platform-gateway-token': this.gatewayToken ?? '',
|
||||||
|
...(init?.headers ?? {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user