Kubernetes refactor

pull/273/head
Frostebite 2021-06-06 20:39:06 +01:00
parent 7206c81db4
commit 9309e3aa91
5 changed files with 125 additions and 97 deletions

109
dist/index.js vendored
View File

@ -50,7 +50,7 @@ function run() {
switch (buildParameters.remoteBuildCluster) { switch (buildParameters.remoteBuildCluster) {
case 'k8s': case 'k8s':
core.info('Building with Kubernetes'); core.info('Building with Kubernetes');
yield model_1.Kubernetes.run(buildParameters, baseImage); yield new model_1.Kubernetes(buildParameters, baseImage).run();
break; break;
case 'aws': case 'aws':
core.info('Building with AWS'); core.info('Building with AWS');
@ -736,39 +736,41 @@ const stream_1 = __webpack_require__(92413);
const base64 = __webpack_require__(85848); const base64 = __webpack_require__(85848);
const pollInterval = 20000; const pollInterval = 20000;
class Kubernetes { class Kubernetes {
static run(buildParameters, baseImage) { constructor(buildParameters, baseImage) {
core.info('Starting up k8s');
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api);
core.info('loaded from default');
const buildId = Kubernetes.uuidv4();
const pvcName = `unity-builder-pvc-${buildId}`;
const secretName = `build-credentials-${buildId}`;
const jobName = `unity-builder-job-${buildId}`;
const namespace = 'default';
this.kubeConfig = kc;
this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi;
this.buildId = buildId;
this.pvcName = pvcName;
this.secretName = secretName;
this.jobName = jobName;
this.namespace = namespace;
this.buildParameters = buildParameters;
this.baseImage = baseImage;
}
run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('Starting up k8s');
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api);
core.info('loaded from default');
const buildId = Kubernetes.uuidv4();
const pvcName = `unity-builder-pvc-${buildId}`;
const secretName = `build-credentials-${buildId}`;
const jobName = `unity-builder-job-${buildId}`;
const namespace = 'default';
this.kubeConfig = kc;
this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi;
this.buildId = buildId;
this.buildParameters = buildParameters;
this.baseImage = baseImage;
this.pvcName = pvcName;
this.secretName = secretName;
this.jobName = jobName;
this.namespace = namespace;
// setup // setup
yield Kubernetes.createSecret(); yield this.createSecret();
yield Kubernetes.createPersistentVolumeClaim(); yield this.createPersistentVolumeClaim();
// start // run
yield Kubernetes.runCloneJob(); yield this.runCloneJob();
yield Kubernetes.runBuildJob(); yield this.runBuildJob();
core.setOutput('volume', pvcName); core.setOutput('volume', this.pvcName);
}); });
} }
static createSecret() { createSecret() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const secret = new k8s.V1Secret(); const secret = new k8s.V1Secret();
secret.apiVersion = 'v1'; secret.apiVersion = 'v1';
@ -787,7 +789,7 @@ class Kubernetes {
yield this.kubeClient.createNamespacedSecret(this.namespace, secret); yield this.kubeClient.createNamespacedSecret(this.namespace, secret);
}); });
} }
static createPersistentVolumeClaim() { createPersistentVolumeClaim() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (this.buildParameters.kubeVolume) { if (this.buildParameters.kubeVolume) {
core.info(this.buildParameters.kubeVolume); core.info(this.buildParameters.kubeVolume);
@ -813,7 +815,7 @@ class Kubernetes {
core.info('Persistent Volume created, waiting for ready state...'); core.info('Persistent Volume created, waiting for ready state...');
}); });
} }
static runJob(command, image) { runJob(command, image) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('Creating build job'); core.info('Creating build job');
const job = new k8s.V1Job(); const job = new k8s.V1Job();
@ -941,25 +943,36 @@ class Kubernetes {
yield this.kubeClientBatch.createNamespacedJob(this.namespace, job); yield this.kubeClientBatch.createNamespacedJob(this.namespace, job);
core.info('Job created'); core.info('Job created');
try { try {
yield Kubernetes.watchPersistentVolumeClaimUntilReady();
// We watch the PVC first to allow some time for K8s to notice the job we created and setup a pod. // We watch the PVC first to allow some time for K8s to notice the job we created and setup a pod.
// TODO: Wait for something more reliable. yield this.watchPersistentVolumeClaimUntilReady();
const pod = (yield this.kubeClient.listNamespacedPod(this.namespace)).body.items.find((x) => { var _a, _b; return ((_b = (_a = x.metadata) === null || _a === void 0 ? void 0 : _a.labels) === null || _b === void 0 ? void 0 : _b['job-name']) === this.jobName; }); // TODO: Wait for something more reliable so we don't potentially get the pod before k8s has created it based on the job.
Kubernetes.setPod(pod); yield this.getPodAndCache();
yield Kubernetes.watchUntilPodRunning(); yield this.watchUntilPodRunning();
yield Kubernetes.streamLogs(); yield this.streamLogs();
} }
catch (error) { catch (error) {
core.error(JSON.stringify(error, undefined, 4)); core.error(JSON.stringify(error, undefined, 4));
} }
finally { finally {
yield Kubernetes.cleanup(); yield this.cleanup();
} }
}); });
} }
static runCloneJob() { getPodAndCache() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield Kubernetes.runJob([ if (this.podName === undefined) {
const pod = (yield this.kubeClient.listNamespacedPod(this.namespace)).body.items.find((x) => { var _a, _b; return ((_b = (_a = x.metadata) === null || _a === void 0 ? void 0 : _a.labels) === null || _b === void 0 ? void 0 : _b['job-name']) === this.jobName; });
this.setPod(pod);
return pod;
}
else {
return (yield this.kubeClient.readNamespacedPod(this.podName, this.namespace)).body;
}
});
}
runCloneJob() {
return __awaiter(this, void 0, void 0, function* () {
yield this.runJob([
'/bin/ash', '/bin/ash',
'-c', '-c',
`apk update; `apk update;
@ -974,7 +987,7 @@ class Kubernetes {
], 'alpine/git'); ], 'alpine/git');
}); });
} }
static runBuildJob() { runBuildJob() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield this.runJob([ yield this.runJob([
'bin/bash', 'bin/bash',
@ -994,20 +1007,20 @@ class Kubernetes {
], this.baseImage.toString()); ], this.baseImage.toString());
}); });
} }
static watchPersistentVolumeClaimUntilReady() { watchPersistentVolumeClaimUntilReady() {
var _a; var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield new Promise((resolve) => setTimeout(resolve, pollInterval)); yield new Promise((resolve) => setTimeout(resolve, pollInterval));
const queryResult = yield this.kubeClient.readNamespacedPersistentVolumeClaim(this.pvcName, this.namespace); const queryResult = yield this.kubeClient.readNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
if (((_a = queryResult.body.status) === null || _a === void 0 ? void 0 : _a.phase) === 'Pending') { if (((_a = queryResult.body.status) === null || _a === void 0 ? void 0 : _a.phase) === 'Pending') {
yield Kubernetes.watchPersistentVolumeClaimUntilReady(); yield this.watchPersistentVolumeClaimUntilReady();
} }
else { else {
core.info('Persistent Volume ready for claims'); core.info('Persistent Volume ready for claims');
} }
}); });
} }
static watchUntilPodRunning() { watchUntilPodRunning() {
var _a, _b; var _a, _b;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
let ready = false; let ready = false;
@ -1029,12 +1042,12 @@ class Kubernetes {
} }
}); });
} }
static setPod(pod) { setPod(pod) {
var _a, _b, _c; var _a, _b, _c;
this.podName = ((_a = pod === null || pod === void 0 ? void 0 : pod.metadata) === null || _a === void 0 ? void 0 : _a.name) || ''; this.podName = ((_a = pod === null || pod === void 0 ? void 0 : pod.metadata) === null || _a === void 0 ? void 0 : _a.name) || '';
this.containerName = ((_c = (_b = pod === null || pod === void 0 ? void 0 : pod.status) === null || _b === void 0 ? void 0 : _b.containerStatuses) === null || _c === void 0 ? void 0 : _c[0].name) || ''; this.containerName = ((_c = (_b = pod === null || pod === void 0 ? void 0 : pod.status) === null || _b === void 0 ? void 0 : _b.containerStatuses) === null || _c === void 0 ? void 0 : _c[0].name) || '';
} }
static streamLogs() { streamLogs() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
core.info(`Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`); core.info(`Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`);
@ -1052,7 +1065,7 @@ class Kubernetes {
} }
}); });
} }
static cleanup() { cleanup() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('cleaning up'); core.info('cleaning up');
yield this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace); yield this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ async function run() {
switch (buildParameters.remoteBuildCluster) { switch (buildParameters.remoteBuildCluster) {
case 'k8s': case 'k8s':
core.info('Building with Kubernetes'); core.info('Building with Kubernetes');
await Kubernetes.run(buildParameters, baseImage); await new Kubernetes(buildParameters, baseImage).run();
break; break;
case 'aws': case 'aws':

View File

@ -3,25 +3,26 @@ import { BuildParameters } from '.';
import * as core from '@actions/core'; import * as core from '@actions/core';
import { KubeConfig, Log } from '@kubernetes/client-node'; import { KubeConfig, Log } from '@kubernetes/client-node';
import { Writable } from 'stream'; import { Writable } from 'stream';
import { RemoteBuilderProviderInterface } from './remote-builder/remote-builder-provider-interface';
const base64 = require('base-64'); const base64 = require('base-64');
const pollInterval = 20000; const pollInterval = 20000;
class Kubernetes { class Kubernetes implements RemoteBuilderProviderInterface {
private static kubeConfig: KubeConfig; private kubeConfig: KubeConfig;
private static kubeClient: k8s.CoreV1Api; private kubeClient: k8s.CoreV1Api;
private static kubeClientBatch: k8s.BatchV1Api; private kubeClientBatch: k8s.BatchV1Api;
private static buildId: string; private buildId: string;
private static buildParameters: BuildParameters; private buildParameters: BuildParameters;
private static baseImage: any; private baseImage: any;
private static pvcName: string; private pvcName: string;
private static secretName: string; private secretName: string;
private static jobName: string; private jobName: string;
private static podName: string; private podName: string | any;
private static containerName: string; private containerName: string | any;
private static namespace: string; private namespace: string;
static async run(buildParameters: BuildParameters, baseImage) { constructor(buildParameters: BuildParameters, baseImage) {
core.info('Starting up k8s'); core.info('Starting up k8s');
const kc = new k8s.KubeConfig(); const kc = new k8s.KubeConfig();
kc.loadFromDefault(); kc.loadFromDefault();
@ -39,25 +40,27 @@ class Kubernetes {
this.kubeClient = k8sApi; this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi; this.kubeClientBatch = k8sBatchApi;
this.buildId = buildId; this.buildId = buildId;
this.buildParameters = buildParameters;
this.baseImage = baseImage;
this.pvcName = pvcName; this.pvcName = pvcName;
this.secretName = secretName; this.secretName = secretName;
this.jobName = jobName; this.jobName = jobName;
this.namespace = namespace; this.namespace = namespace;
this.buildParameters = buildParameters;
// setup this.baseImage = baseImage;
await Kubernetes.createSecret();
await Kubernetes.createPersistentVolumeClaim();
// start
await Kubernetes.runCloneJob();
await Kubernetes.runBuildJob();
core.setOutput('volume', pvcName);
} }
static async createSecret() { async run() {
// setup
await this.createSecret();
await this.createPersistentVolumeClaim();
// run
await this.runCloneJob();
await this.runBuildJob();
core.setOutput('volume', this.pvcName);
}
async createSecret() {
const secret = new k8s.V1Secret(); const secret = new k8s.V1Secret();
secret.apiVersion = 'v1'; secret.apiVersion = 'v1';
secret.kind = 'Secret'; secret.kind = 'Secret';
@ -77,7 +80,7 @@ class Kubernetes {
await this.kubeClient.createNamespacedSecret(this.namespace, secret); await this.kubeClient.createNamespacedSecret(this.namespace, secret);
} }
static async createPersistentVolumeClaim() { async createPersistentVolumeClaim() {
if (this.buildParameters.kubeVolume) { if (this.buildParameters.kubeVolume) {
core.info(this.buildParameters.kubeVolume); core.info(this.buildParameters.kubeVolume);
this.pvcName = this.buildParameters.kubeVolume; this.pvcName = this.buildParameters.kubeVolume;
@ -102,7 +105,7 @@ class Kubernetes {
core.info('Persistent Volume created, waiting for ready state...'); core.info('Persistent Volume created, waiting for ready state...');
} }
static async runJob(command: string[], image: string) { async runJob(command: string[], image: string) {
core.info('Creating build job'); core.info('Creating build job');
const job = new k8s.V1Job(); const job = new k8s.V1Job();
job.apiVersion = 'batch/v1'; job.apiVersion = 'batch/v1';
@ -230,24 +233,33 @@ class Kubernetes {
core.info('Job created'); core.info('Job created');
try { try {
await Kubernetes.watchPersistentVolumeClaimUntilReady();
// We watch the PVC first to allow some time for K8s to notice the job we created and setup a pod. // We watch the PVC first to allow some time for K8s to notice the job we created and setup a pod.
// TODO: Wait for something more reliable. await this.watchPersistentVolumeClaimUntilReady();
const pod = (await this.kubeClient.listNamespacedPod(this.namespace)).body.items.find( // TODO: Wait for something more reliable so we don't potentially get the pod before k8s has created it based on the job.
(x) => x.metadata?.labels?.['job-name'] === this.jobName, await this.getPodAndCache();
); await this.watchUntilPodRunning();
Kubernetes.setPod(pod); await this.streamLogs();
await Kubernetes.watchUntilPodRunning();
await Kubernetes.streamLogs();
} catch (error) { } catch (error) {
core.error(JSON.stringify(error, undefined, 4)); core.error(JSON.stringify(error, undefined, 4));
} finally { } finally {
await Kubernetes.cleanup(); await this.cleanup();
} }
} }
static async runCloneJob() { async getPodAndCache() {
await Kubernetes.runJob( if (this.podName === undefined) {
const pod = (await this.kubeClient.listNamespacedPod(this.namespace)).body.items.find(
(x) => x.metadata?.labels?.['job-name'] === this.jobName,
);
this.setPod(pod);
return pod;
} else {
return (await this.kubeClient.readNamespacedPod(this.podName, this.namespace)).body;
}
}
async runCloneJob() {
await this.runJob(
[ [
'/bin/ash', '/bin/ash',
'-c', '-c',
@ -265,7 +277,7 @@ class Kubernetes {
); );
} }
static async runBuildJob() { async runBuildJob() {
await this.runJob( await this.runJob(
[ [
'bin/bash', 'bin/bash',
@ -287,18 +299,18 @@ class Kubernetes {
); );
} }
static async watchPersistentVolumeClaimUntilReady() { async watchPersistentVolumeClaimUntilReady() {
await new Promise((resolve) => setTimeout(resolve, pollInterval)); await new Promise((resolve) => setTimeout(resolve, pollInterval));
const queryResult = await this.kubeClient.readNamespacedPersistentVolumeClaim(this.pvcName, this.namespace); const queryResult = await this.kubeClient.readNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
if (queryResult.body.status?.phase === 'Pending') { if (queryResult.body.status?.phase === 'Pending') {
await Kubernetes.watchPersistentVolumeClaimUntilReady(); await this.watchPersistentVolumeClaimUntilReady();
} else { } else {
core.info('Persistent Volume ready for claims'); core.info('Persistent Volume ready for claims');
} }
} }
static async watchUntilPodRunning() { async watchUntilPodRunning() {
let ready = false; let ready = false;
while (!ready) { while (!ready) {
@ -319,12 +331,12 @@ class Kubernetes {
} }
} }
static setPod(pod: k8s.V1Pod | any) { setPod(pod: k8s.V1Pod | any) {
this.podName = pod?.metadata?.name || ''; this.podName = pod?.metadata?.name || '';
this.containerName = pod?.status?.containerStatuses?.[0].name || ''; this.containerName = pod?.status?.containerStatuses?.[0].name || '';
} }
static async streamLogs() { async streamLogs() {
try { try {
core.info( core.info(
`Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`, `Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`,
@ -344,7 +356,7 @@ class Kubernetes {
} }
} }
static async cleanup() { async cleanup() {
core.info('cleaning up'); core.info('cleaning up');
await this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace); await this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
await this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace); await this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);

View File

@ -0,0 +1,3 @@
export interface RemoteBuilderProviderInterface {
run(): Promise<void>;
}