mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-01-16 00:39:47 -06:00
Intergrating Building
This commit is contained in:
@@ -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<typeof getK8sCoreApiClient>;
|
||||
k8sAppsGlobal: ReturnType<typeof getK8sAppsApiClient>;
|
||||
k8sJobGlobal: ReturnType<typeof getK8sBatchApiClient>;
|
||||
} & 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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
113
src/server/services/build.service.ts
Normal file
113
src/server/services/build.service.ts
Normal file
@@ -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<void>((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;
|
||||
Reference in New Issue
Block a user