From d56ecffae6116ea63d6fab590cc59020ba66b784 Mon Sep 17 00:00:00 2001 From: "stefan.meyer" Date: Thu, 31 Oct 2024 14:04:59 +0000 Subject: [PATCH] Intergrating Building --- .gitignore | 1 + bun.lockb | Bin 285960 -> 285960 bytes src/server/adapter/kubernetes-api.adapter.ts | 12 ++ src/server/services/app.service.ts | 3 + src/server/services/build.service.ts | 113 +++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 src/server/services/build.service.ts diff --git a/.gitignore b/.gitignore index d076ca1..46d03f2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ db kube-config.config kube-config.config_clusteradmin kube-config.config_old +kube-config.config_restricted diff --git a/bun.lockb b/bun.lockb index 1bd2d70ba2621e2709509bc0fbeb387b73c2c2af..11919173b00130f597175b1b839edfe0b737eda5 100755 GIT binary patch delta 35 rcmeC!CD^e`u%U%<3sdV&cE&hUJp(<%X)Mel?F())ZC`McIinc><_`?% delta 35 ocmeC!CD^e`u%U%<3sdV&HYNrTnC_^-EZV-{Ce!u>H<>e<0o1Mx00000 diff --git a/src/server/adapter/kubernetes-api.adapter.ts b/src/server/adapter/kubernetes-api.adapter.ts index f6e9fe2..d8c845c 100644 --- a/src/server/adapter/kubernetes-api.adapter.ts +++ b/src/server/adapter/kubernetes-api.adapter.ts @@ -14,9 +14,17 @@ const getK8sAppsApiClient = () => { return k8sCoreClient; } +const getK8sBatchApiClient = () => { + const kc = new k8s.KubeConfig(); + kc.loadFromFile('/workspace/kube-config.config'); // todo update --> use security role + const k8sJobClient = kc.makeApiClient(k8s.BatchV1Api); + return k8sJobClient; +} + declare const globalThis: { k8sCoreGlobal: ReturnType; k8sAppsGlobal: ReturnType; + k8sJobGlobal: ReturnType; } & typeof global; const k8sCoreClient = globalThis.k8sCoreGlobal ?? getK8sCoreApiClient() @@ -26,9 +34,13 @@ if (process.env.NODE_ENV !== 'production') globalThis.k8sCoreGlobal = k8sCoreCli const k8sAppsClient = globalThis.k8sAppsGlobal ?? getK8sAppsApiClient() if (process.env.NODE_ENV !== 'production') globalThis.k8sAppsGlobal = k8sAppsClient +const k8sJobClient = globalThis.k8sJobGlobal ?? getK8sBatchApiClient() +if (process.env.NODE_ENV !== 'production') globalThis.k8sJobGlobal = k8sJobClient + class K3sApiAdapter { core = k8sCoreClient; apps = k8sAppsClient; + batch = k8sJobClient; } const k3s = new K3sApiAdapter(); diff --git a/src/server/services/app.service.ts b/src/server/services/app.service.ts index bfc33ef..47fd426 100644 --- a/src/server/services/app.service.ts +++ b/src/server/services/app.service.ts @@ -7,6 +7,7 @@ import { AppExtendedModel } from "@/model/app-extended.model"; import { ServiceException } from "@/model/service.exception.model"; import { StringUtils } from "../utils/string.utils"; import deploymentService from "./deployment.service"; +import buildService, { buildNamespace } from "./build.service"; class AppService { @@ -14,6 +15,8 @@ class AppService { const app = await this.getExtendedById(appId); if (app.sourceType === 'GIT') { // first make build + await deploymentService.createNamespaceIfNotExists(buildNamespace) + await buildService.buildApp(app); } else { // only deploy await deploymentService.createDeployment(app); diff --git a/src/server/services/build.service.ts b/src/server/services/build.service.ts new file mode 100644 index 0000000..fecc566 --- /dev/null +++ b/src/server/services/build.service.ts @@ -0,0 +1,113 @@ +import { AppExtendedModel } from "@/model/app-extended.model"; +import k3s from "../adapter/kubernetes-api.adapter"; +import { V1Deployment, V1Job } from "@kubernetes/client-node"; + +const kanikoImage = "gcr.io/kaniko-project/executor:latest"; +export const registryURL = "registry-svc.registry-and-build.svc.cluster.local" +export const buildNamespace = "registry-and-build"; + +class BuildService { + + async buildApp(app: AppExtendedModel) { + const jobDefinition: V1Job = { + apiVersion: "batch/v1", + kind: "Job", + metadata: { + name: `build-${app.id}`, + namespace: buildNamespace, + }, + spec: { + ttlSecondsAfterFinished: 100, + template: { + spec: { + containers: [ + { + name: `build-${app.id}`, + image: kanikoImage, + args: [`--dockerfile=${app.dockerfilePath}`, + `--context=${app.gitUrl!.replace("https://","git://")}#refs/heads/${app.gitBranch}`, + `--destination=${registryURL}/${app.id}`] + }, + ], + restartPolicy: "Never", + + }, + }, + backoffLimit: 0, + }, + }; + if (app.gitUsername && app.gitToken) { + jobDefinition.spec!.template.spec!.containers[0].env = [ + { + name: "GIT_USERNAME", + value: app.gitUsername + }, + { + name: "GIT_PASSWORD", + value: app.gitToken + } + ]; + } + await k3s.batch.createNamespacedJob(buildNamespace, jobDefinition); + await this.waitForJobCompletion(buildNamespace, jobDefinition.metadata!.name!); + } + + async waitForJobCompletion(namespace:string, jobName:string) { + const POLL_INTERVAL = 10000; // 10 seconds + + return await new Promise((resolve, reject) => { + const intervalId = setInterval(async () => { + try { + const jobStatus = await this.getJobStatus(namespace, jobName); + if (jobStatus === 'UNKNOWN') { + console.log(`Job ${jobName} not found.`); + clearInterval(intervalId); + reject(new Error(`Job ${jobName} not found.`)); + return; + } + if (jobStatus === 'SUCCEEDED') { + clearInterval(intervalId); + console.log(`Job ${jobName} completed successfully.`); + resolve(); + } else if (jobStatus === 'FAILED') { + clearInterval(intervalId); + console.log(`Job ${jobName} failed.`); + reject(new Error(`Job ${jobName} failed.`)); + } else { + console.log(`Job ${jobName} is still running...`); + } + } catch (err) { + clearInterval(intervalId); + reject(err); + } + }, POLL_INTERVAL); + }); + } + + async getJobStatus(namespace:string, jobName:string): Promise<'UNKNOWN' | 'RUNNING' | 'FAILED' | 'SUCCEEDED'> { + try { + const response = await k3s.batch.readNamespacedJobStatus(jobName, namespace); + const job = response.body; + if (!job.status) { + return 'UNKNOWN'; + } + if ((job.status.active ?? 0) > 0) { + return 'RUNNING'; + } + if ((job.status.succeeded?? 0) > 0) { + return 'SUCCEEDED'; + } + + if ((job.status.failed ?? 0) > 0) { + return 'FAILED'; + } + } catch (err) { + console.error(err); + } + return 'UNKNOWN'; + } + +} + +const buildService = new BuildService(); +export default buildService;