mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-01-02 09:41:25 -06:00
created logic to delete all k8s items when app deleted
This commit is contained in:
@@ -24,7 +24,7 @@ export async function POST(request: Request) {
|
||||
let pod;
|
||||
let streamKey;
|
||||
if (namespace && podName) {
|
||||
pod = await deploymentService.getPodByName(namespace, podName);
|
||||
pod = await podService.getPodInfoByName(namespace, podName);
|
||||
streamKey = `${namespace}_${podName}`;
|
||||
|
||||
} else if (buildJobName) {
|
||||
@@ -36,7 +36,6 @@ export async function POST(request: Request) {
|
||||
console.error('Invalid pod info for streaming logs', podInfo);
|
||||
return new Response("Invalid pod info", { status: 400 });
|
||||
}
|
||||
console.log('pod', pod)
|
||||
|
||||
let k3sStreamRequest: any | undefined;
|
||||
let logStream: stream.PassThrough | undefined;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ServerActionResult, SuccessActionResult } from "@/model/server-action-e
|
||||
import appService from "@/server/services/app.service";
|
||||
import buildService from "@/server/services/build.service";
|
||||
import deploymentService from "@/server/services/deployment.service";
|
||||
import podService from "@/server/services/pod.service";
|
||||
import { getAuthUserSession, simpleAction } from "@/server/utils/action-wrapper.utils";
|
||||
|
||||
|
||||
@@ -28,5 +29,5 @@ export const getPodsForApp = async (appId: string) =>
|
||||
simpleAction(async () => {
|
||||
await getAuthUserSession();
|
||||
const app = await appService.getExtendedById(appId);
|
||||
return await deploymentService.getPodsForApp(app.projectId, appId);
|
||||
return await podService.getPodsForApp(app.projectId, appId);
|
||||
}) as Promise<ServerActionResult<unknown, PodsInfoModel[]>>;
|
||||
@@ -9,6 +9,9 @@ import { StringUtils } from "../utils/string.utils";
|
||||
import deploymentService from "./deployment.service";
|
||||
import buildService, { BUILD_NAMESPACE } from "./build.service";
|
||||
import namespaceService from "./namespace.service";
|
||||
import ingressService from "./ingress.service";
|
||||
import pvcService from "./pvc.service";
|
||||
import svcService from "./svc.service";
|
||||
|
||||
class AppService {
|
||||
|
||||
@@ -29,21 +32,24 @@ class AppService {
|
||||
}
|
||||
|
||||
async deleteById(id: string) {
|
||||
const existingItem = await this.getById(id);
|
||||
if (!existingItem) {
|
||||
const existingApp = await this.getById(id);
|
||||
if (!existingApp) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deploymentService.deleteService(existingItem.projectId, existingItem.id);
|
||||
await deploymentService.deleteDeployment(existingItem.projectId, existingItem.id);
|
||||
await svcService.deleteService(existingApp.projectId, existingApp.id);
|
||||
await deploymentService.deleteDeployment(existingApp.projectId, existingApp.id);
|
||||
await ingressService.deleteAllIngressForApp(existingApp.projectId, existingApp.id);
|
||||
await pvcService.deleteAllPvcOfApp(existingApp.projectId, existingApp.id);
|
||||
await buildService.deleteAllBuildsOfApp(existingApp.id);
|
||||
await dataAccess.client.app.delete({
|
||||
where: {
|
||||
id
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
revalidateTag(Tags.apps(existingItem.projectId));
|
||||
revalidateTag(Tags.app(existingItem.id));
|
||||
revalidateTag(Tags.apps(existingApp.projectId));
|
||||
revalidateTag(Tags.app(existingApp.id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,8 +96,27 @@ class BuildService {
|
||||
return `${REGISTRY_URL_EXTERNAL}/${appId}:latest`;
|
||||
}
|
||||
|
||||
|
||||
async deleteAllBuildsOfApp(appId: string) {
|
||||
const jobNamePrefix = StringUtils.toJobName(appId);
|
||||
const jobs = await k3s.batch.listNamespacedJob(BUILD_NAMESPACE);
|
||||
const jobsOfBuild = jobs.body.items.filter((job) => job.metadata?.name?.startsWith(jobNamePrefix));
|
||||
for (const job of jobsOfBuild) {
|
||||
await this.deleteBuild(job.metadata?.name!);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAllBuildsOfProject(projectId: string) {
|
||||
const jobs = await k3s.batch.listNamespacedJob(BUILD_NAMESPACE);
|
||||
const jobsOfProject = jobs.body.items.filter((job) => job.metadata?.annotations?.[Constants.QS_ANNOTATION_PROJECT_ID] === projectId);
|
||||
for (const job of jobsOfProject) {
|
||||
await this.deleteBuild(job.metadata?.name!);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteBuild(buildName: string) {
|
||||
await k3s.batch.deleteNamespacedJob(buildName, BUILD_NAMESPACE);
|
||||
console.log(`Deleted build job ${buildName}`);
|
||||
}
|
||||
|
||||
async getBuildsForApp(appId: string) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import pvcService from "./pvc.service";
|
||||
import ingressService from "./ingress.service";
|
||||
import namespaceService from "./namespace.service";
|
||||
import { Constants } from "../utils/constants";
|
||||
import svcService from "./svc.service";
|
||||
|
||||
class DeploymentService {
|
||||
|
||||
@@ -28,64 +29,9 @@ class DeploymentService {
|
||||
if (!existingDeployment) {
|
||||
return;
|
||||
}
|
||||
return k3s.apps.deleteNamespacedDeployment(appId, projectId);
|
||||
}
|
||||
|
||||
async deleteService(projectId: string, appId: string) {
|
||||
const existingService = await this.getService(projectId, appId);
|
||||
if (!existingService) {
|
||||
return;
|
||||
}
|
||||
return k3s.core.deleteNamespacedService(StringUtils.toServiceName(appId), projectId);
|
||||
}
|
||||
|
||||
|
||||
async getService(projectId: string, appId: string) {
|
||||
const allServices = await k3s.core.listNamespacedService(projectId);
|
||||
if (allServices.body.items.some((item) => item.metadata?.name === StringUtils.toServiceName(appId))) {
|
||||
const res = await k3s.core.readNamespacedService(StringUtils.toServiceName(appId), projectId);
|
||||
return res.body;
|
||||
}
|
||||
}
|
||||
|
||||
async createOrUpdateService(app: AppExtendedModel) {
|
||||
const existingService = await this.getService(app.projectId, app.id);
|
||||
// port configuration with removed duplicates
|
||||
const ports: {
|
||||
name: string;
|
||||
port: number;
|
||||
targetPort: number;
|
||||
}[] = [
|
||||
...app.appDomains.map((domain) => ({
|
||||
name: `custom-${domain.id}`,
|
||||
port: domain.port,
|
||||
targetPort: domain.port
|
||||
})),
|
||||
{
|
||||
name: 'default',
|
||||
port: app.defaultPort,
|
||||
targetPort: app.defaultPort
|
||||
}
|
||||
].filter((port, index, self) =>
|
||||
index === self.findIndex((t) => (t.port === port.port && t.targetPort === port.targetPort)));
|
||||
|
||||
const body = {
|
||||
metadata: {
|
||||
name: StringUtils.toServiceName(app.id)
|
||||
},
|
||||
spec: {
|
||||
selector: {
|
||||
app: app.id
|
||||
},
|
||||
ports: ports
|
||||
}
|
||||
};
|
||||
if (existingService) {
|
||||
await k3s.core.replaceNamespacedService(StringUtils.toServiceName(app.id), app.projectId, body);
|
||||
} else {
|
||||
await k3s.core.createNamespacedService(app.projectId, body);
|
||||
}
|
||||
|
||||
const returnVal = await k3s.apps.deleteNamespacedDeployment(appId, projectId);
|
||||
console.log(`Deleted Deployment ${appId} in namespace ${projectId}`);
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
async validateDeployment(app: AppExtendedModel) {
|
||||
@@ -169,7 +115,7 @@ class DeploymentService {
|
||||
const res = await k3s.apps.createNamespacedDeployment(app.projectId, body);
|
||||
}
|
||||
await pvcService.deleteUnusedPvcOfApp(app);
|
||||
await this.createOrUpdateService(app);
|
||||
await svcService.createOrUpdateService(app);
|
||||
await ingressService.createOrUpdateIngressForApp(app);
|
||||
}
|
||||
|
||||
@@ -190,13 +136,6 @@ class DeploymentService {
|
||||
return k3s.apps.replaceNamespacedDeployment(appId, projectId, existingDeployment);
|
||||
}
|
||||
|
||||
async getPodsForApp(projectId: string, appId: string) {
|
||||
const res = await k3s.core.listNamespacedPod(projectId, undefined, undefined, undefined, undefined, `app=${appId}`);
|
||||
return res.body.items.map((item) => ({
|
||||
podName: item.metadata?.name!,
|
||||
containerName: item.spec?.containers?.[0].name!
|
||||
})).filter((item) => !!item.podName && !!item.containerName) as PodsInfoModel[];
|
||||
}
|
||||
|
||||
async getDeploymentStatus(projectId: string, appId: string) {
|
||||
const deployment = await this.getDeployment(projectId, appId);
|
||||
@@ -206,14 +145,6 @@ class DeploymentService {
|
||||
return this.mapReplicasetToStatus(deployment);
|
||||
}
|
||||
|
||||
async getPodByName(projectId: string, podName: string) {
|
||||
const res = await k3s.core.readNamespacedPod(podName, projectId);
|
||||
return {
|
||||
podName: res.body.metadata?.name!,
|
||||
containerName: res.body.spec?.containers?.[0].name!
|
||||
} as PodsInfoModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for Build Jobs (only for Git Projects) and ReplicaSets (for all projects) and returns a list of DeploymentModel
|
||||
* Build are only included if they are in status RUNNING, FAILED or UNKNOWN. SUCCESSFUL builds are not included because they are already part of the ReplicaSet history.
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AppExtendedModel } from "@/model/app-extended.model";
|
||||
import k3s from "../adapter/kubernetes-api.adapter";
|
||||
import { V1Ingress } from "@kubernetes/client-node";
|
||||
import { StringUtils } from "../utils/string.utils";
|
||||
import { AppDomain } from "@prisma/client";
|
||||
import { App, AppDomain } from "@prisma/client";
|
||||
import { Constants } from "../utils/constants";
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class IngressService {
|
||||
}
|
||||
|
||||
|
||||
async deleteObsoleteIngresses(app: AppExtendedModel) {
|
||||
async deleteUnusedIngressesOfApp(app: AppExtendedModel) {
|
||||
const currentDomains = new Set(app.appDomains.map(domainObj => domainObj.hostname));
|
||||
const existingIngresses = await this.getAllIngressForApp(app.projectId, app.id);
|
||||
|
||||
@@ -42,6 +42,14 @@ class IngressService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAllIngressForApp(projectId: string, appId: string) {
|
||||
const existingIngresses = await this.getAllIngressForApp(projectId, appId);
|
||||
for (const ingress of existingIngresses) {
|
||||
await k3s.network.deleteNamespacedIngress(ingress.metadata!.name!, projectId);
|
||||
console.log(`Deleted Ingress ${ingress.metadata!.name} for app ${appId}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async createOrUpdateIngressForApp(app: AppExtendedModel) {
|
||||
|
||||
@@ -51,7 +59,7 @@ class IngressService {
|
||||
await this.createIngress(app, domainObj);
|
||||
}
|
||||
|
||||
await this.deleteObsoleteIngresses(app);
|
||||
await this.deleteUnusedIngressesOfApp(app);
|
||||
}
|
||||
|
||||
async createIngress(app: AppExtendedModel, domain: AppDomain) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PodsInfoModel } from "@/model/pods-info.model";
|
||||
import k3s from "../adapter/kubernetes-api.adapter";
|
||||
import { ServiceException } from "@/model/service.exception.model";
|
||||
|
||||
@@ -27,6 +28,24 @@ class PodService {
|
||||
const res = await k3s.core.readNamespacedPod(podName, projectId);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
|
||||
async getPodInfoByName(projectId: string, podName: string) {
|
||||
const res = await k3s.core.readNamespacedPod(podName, projectId);
|
||||
return {
|
||||
podName: res.body.metadata?.name!,
|
||||
containerName: res.body.spec?.containers?.[0].name!
|
||||
} as PodsInfoModel;
|
||||
}
|
||||
|
||||
|
||||
async getPodsForApp(projectId: string, appId: string) {
|
||||
const res = await k3s.core.listNamespacedPod(projectId, undefined, undefined, undefined, undefined, `app=${appId}`);
|
||||
return res.body.items.map((item) => ({
|
||||
podName: item.metadata?.name!,
|
||||
containerName: item.spec?.containers?.[0].name!
|
||||
})).filter((item) => !!item.podName && !!item.containerName) as PodsInfoModel[];
|
||||
}
|
||||
}
|
||||
|
||||
const podService = new PodService();
|
||||
|
||||
@@ -5,19 +5,21 @@ import { Prisma, Project } from "@prisma/client";
|
||||
import { StringUtils } from "../utils/string.utils";
|
||||
import deploymentService from "./deployment.service";
|
||||
import namespaceService from "./namespace.service";
|
||||
import buildService from "./build.service";
|
||||
|
||||
class ProjectService {
|
||||
|
||||
async deleteById(id: string) {
|
||||
const existingItem = await this.getById(id);
|
||||
async deleteById(projectid: string) {
|
||||
const existingItem = await this.getById(projectid);
|
||||
if (!existingItem) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await buildService.deleteAllBuildsOfProject(existingItem.id);
|
||||
await namespaceService.deleteNamespace(existingItem.id);
|
||||
await dataAccess.client.project.delete({
|
||||
where: {
|
||||
id
|
||||
id: projectid
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -40,6 +40,15 @@ class PvcService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAllPvcOfApp(projectId: string, appId: string) {
|
||||
const existingPvc = await this.getAllPvcForApp(projectId, appId);
|
||||
|
||||
for (const pvc of existingPvc) {
|
||||
await k3s.core.deleteNamespacedPersistentVolumeClaim(pvc.metadata!.name!, projectId);
|
||||
console.log(`Deleted PVC ${pvc.metadata!.name!} for app ${appId}`);
|
||||
}
|
||||
}
|
||||
|
||||
async createOrUpdatePvc(app: AppExtendedModel) {
|
||||
const existingPvcs = await this.getAllPvcForApp(app.projectId, app.id);
|
||||
|
||||
|
||||
72
src/server/services/svc.service.ts
Normal file
72
src/server/services/svc.service.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { AppExtendedModel } from "@/model/app-extended.model";
|
||||
import k3s from "../adapter/kubernetes-api.adapter";
|
||||
import { V1PersistentVolumeClaim } from "@kubernetes/client-node";
|
||||
import { ServiceException } from "@/model/service.exception.model";
|
||||
import { AppVolume } from "@prisma/client";
|
||||
import { StringUtils } from "../utils/string.utils";
|
||||
import { Constants } from "../utils/constants";
|
||||
|
||||
class SvcService {
|
||||
|
||||
async deleteService(projectId: string, appId: string) {
|
||||
const existingService = await this.getService(projectId, appId);
|
||||
if (!existingService) {
|
||||
return;
|
||||
}
|
||||
const returnVal = await k3s.core.deleteNamespacedService(StringUtils.toServiceName(appId), projectId);
|
||||
console.log(`Deleted Service ${StringUtils.toServiceName(appId)} in namespace ${projectId}`);
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
|
||||
async getService(projectId: string, appId: string) {
|
||||
const allServices = await k3s.core.listNamespacedService(projectId);
|
||||
if (allServices.body.items.some((item) => item.metadata?.name === StringUtils.toServiceName(appId))) {
|
||||
const res = await k3s.core.readNamespacedService(StringUtils.toServiceName(appId), projectId);
|
||||
return res.body;
|
||||
}
|
||||
}
|
||||
|
||||
async createOrUpdateService(app: AppExtendedModel) {
|
||||
const existingService = await this.getService(app.projectId, app.id);
|
||||
// port configuration with removed duplicates
|
||||
const ports: {
|
||||
name: string;
|
||||
port: number;
|
||||
targetPort: number;
|
||||
}[] = [
|
||||
...app.appDomains.map((domain) => ({
|
||||
name: `custom-${domain.id}`,
|
||||
port: domain.port,
|
||||
targetPort: domain.port
|
||||
})),
|
||||
{
|
||||
name: 'default',
|
||||
port: app.defaultPort,
|
||||
targetPort: app.defaultPort
|
||||
}
|
||||
].filter((port, index, self) =>
|
||||
index === self.findIndex((t) => (t.port === port.port && t.targetPort === port.targetPort)));
|
||||
|
||||
const body = {
|
||||
metadata: {
|
||||
name: StringUtils.toServiceName(app.id)
|
||||
},
|
||||
spec: {
|
||||
selector: {
|
||||
app: app.id
|
||||
},
|
||||
ports: ports
|
||||
}
|
||||
};
|
||||
if (existingService) {
|
||||
await k3s.core.replaceNamespacedService(StringUtils.toServiceName(app.id), app.projectId, body);
|
||||
} else {
|
||||
await k3s.core.createNamespacedService(app.projectId, body);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const svcService = new SvcService();
|
||||
export default svcService;
|
||||
Reference in New Issue
Block a user