2021-05-23 13:31:02 +00:00
|
|
|
import * as k8s from '@kubernetes/client-node';
|
2021-09-22 20:05:21 +00:00
|
|
|
import { BuildParameters } from '../..';
|
2021-05-28 17:54:54 +00:00
|
|
|
import * as core from '@actions/core';
|
2021-10-04 23:23:49 +00:00
|
|
|
import { CloudRunnerProviderInterface } from '../services/cloud-runner-provider-interface';
|
|
|
|
|
import CloudRunnerSecret from '../services/cloud-runner-secret';
|
2021-06-18 19:52:07 +00:00
|
|
|
import KubernetesStorage from './kubernetes-storage';
|
2021-10-04 23:23:49 +00:00
|
|
|
import CloudRunnerEnvironmentVariable from '../services/cloud-runner-environment-variable';
|
2021-12-29 17:25:38 +00:00
|
|
|
import KubernetesTaskRunner from './kubernetes-task-runner';
|
2021-06-19 23:06:44 +00:00
|
|
|
import KubernetesSecret from './kubernetes-secret';
|
2021-06-26 04:01:43 +00:00
|
|
|
import waitUntil from 'async-wait-until';
|
2021-06-26 01:50:03 +00:00
|
|
|
import KubernetesJobSpecFactory from './kubernetes-job-spec-factory';
|
2021-07-13 00:28:16 +00:00
|
|
|
import KubernetesServiceAccount from './kubernetes-service-account';
|
2021-10-04 23:23:49 +00:00
|
|
|
import CloudRunnerLogger from '../services/cloud-runner-logger';
|
2021-12-29 17:25:38 +00:00
|
|
|
import { CoreV1Api } from '@kubernetes/client-node';
|
2021-06-18 19:27:14 +00:00
|
|
|
|
2021-08-17 20:09:42 +00:00
|
|
|
class Kubernetes implements CloudRunnerProviderInterface {
|
2021-06-19 23:06:44 +00:00
|
|
|
private kubeConfig: k8s.KubeConfig;
|
2021-06-06 19:39:06 +00:00
|
|
|
private kubeClient: k8s.CoreV1Api;
|
|
|
|
|
private kubeClientBatch: k8s.BatchV1Api;
|
2021-08-15 21:59:58 +00:00
|
|
|
private buildGuid: string = '';
|
2021-06-06 19:39:06 +00:00
|
|
|
private buildParameters: BuildParameters;
|
2021-06-06 21:22:22 +00:00
|
|
|
private pvcName: string = '';
|
|
|
|
|
private secretName: string = '';
|
|
|
|
|
private jobName: string = '';
|
2021-06-06 19:39:06 +00:00
|
|
|
private namespace: string;
|
2021-06-06 20:14:12 +00:00
|
|
|
private podName: string = '';
|
|
|
|
|
private containerName: string = '';
|
2021-06-26 01:50:03 +00:00
|
|
|
private cleanupCronJobName: string = '';
|
2021-07-13 00:28:16 +00:00
|
|
|
private serviceAccountName: string = '';
|
2021-03-13 23:44:01 +00:00
|
|
|
|
2021-06-19 20:35:22 +00:00
|
|
|
constructor(buildParameters: BuildParameters) {
|
2021-06-26 01:50:03 +00:00
|
|
|
this.kubeConfig = new k8s.KubeConfig();
|
|
|
|
|
this.kubeConfig.loadFromDefault();
|
|
|
|
|
this.kubeClient = this.kubeConfig.makeApiClient(k8s.CoreV1Api);
|
|
|
|
|
this.kubeClientBatch = this.kubeConfig.makeApiClient(k8s.BatchV1Api);
|
2021-09-21 18:27:04 +00:00
|
|
|
CloudRunnerLogger.log('Loaded default Kubernetes configuration for this environment');
|
2021-05-23 13:31:02 +00:00
|
|
|
|
2021-06-06 21:22:22 +00:00
|
|
|
this.namespace = 'default';
|
|
|
|
|
this.buildParameters = buildParameters;
|
|
|
|
|
}
|
2021-12-25 20:10:12 +00:00
|
|
|
public async setupSharedResources(
|
2021-08-15 21:59:58 +00:00
|
|
|
buildGuid: string,
|
2021-06-19 22:15:44 +00:00
|
|
|
buildParameters: BuildParameters,
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
|
branchName: string,
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
|
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
|
|
|
|
) {
|
2021-06-26 01:54:37 +00:00
|
|
|
try {
|
2021-09-08 20:21:15 +00:00
|
|
|
this.pvcName = `unity-builder-pvc-${buildGuid}`;
|
2021-08-15 21:59:58 +00:00
|
|
|
this.cleanupCronJobName = `unity-builder-cronjob-${buildGuid}`;
|
|
|
|
|
this.serviceAccountName = `service-account-${buildGuid}`;
|
2021-06-26 01:54:37 +00:00
|
|
|
await KubernetesStorage.createPersistentVolumeClaim(
|
|
|
|
|
buildParameters,
|
|
|
|
|
this.pvcName,
|
|
|
|
|
this.kubeClient,
|
|
|
|
|
this.namespace,
|
|
|
|
|
);
|
2021-07-13 00:28:16 +00:00
|
|
|
|
|
|
|
|
await KubernetesServiceAccount.createServiceAccount(this.serviceAccountName, this.namespace, this.kubeClient);
|
2021-06-26 01:54:37 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2021-06-19 22:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
2021-12-25 20:10:12 +00:00
|
|
|
async runTask(
|
2021-08-15 21:59:58 +00:00
|
|
|
buildGuid: string,
|
2021-06-18 20:36:45 +00:00
|
|
|
image: string,
|
2021-12-29 16:28:42 +00:00
|
|
|
commands: string,
|
2021-06-18 20:36:45 +00:00
|
|
|
mountdir: string,
|
|
|
|
|
workingdir: string,
|
2021-08-17 22:13:46 +00:00
|
|
|
environment: CloudRunnerEnvironmentVariable[],
|
|
|
|
|
secrets: CloudRunnerSecret[],
|
2021-12-30 20:25:28 +00:00
|
|
|
): Promise<string> {
|
2021-06-18 20:36:45 +00:00
|
|
|
try {
|
|
|
|
|
// setup
|
2021-08-15 21:59:58 +00:00
|
|
|
this.buildGuid = buildGuid;
|
|
|
|
|
this.secretName = `build-credentials-${buildGuid}`;
|
|
|
|
|
this.jobName = `unity-builder-job-${buildGuid}`;
|
2021-09-15 03:35:57 +00:00
|
|
|
this.containerName = `main`;
|
2021-06-19 23:06:44 +00:00
|
|
|
await KubernetesSecret.createSecret(secrets, this.secretName, this.namespace, this.kubeClient);
|
2021-06-26 01:50:03 +00:00
|
|
|
const jobSpec = KubernetesJobSpecFactory.getJobSpec(
|
|
|
|
|
commands,
|
|
|
|
|
image,
|
|
|
|
|
mountdir,
|
|
|
|
|
workingdir,
|
|
|
|
|
environment,
|
2021-12-29 20:49:13 +00:00
|
|
|
secrets,
|
2021-08-15 21:59:58 +00:00
|
|
|
this.buildGuid,
|
2021-06-26 01:50:03 +00:00
|
|
|
this.buildParameters,
|
|
|
|
|
this.secretName,
|
|
|
|
|
this.pvcName,
|
|
|
|
|
this.jobName,
|
|
|
|
|
k8s,
|
|
|
|
|
);
|
2021-06-18 20:36:45 +00:00
|
|
|
|
2021-06-19 04:27:24 +00:00
|
|
|
//run
|
2022-01-30 17:21:38 +00:00
|
|
|
const jobResult = await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
|
2022-01-30 18:55:49 +00:00
|
|
|
CloudRunnerLogger.log(`Creating build job ${JSON.stringify(jobResult.body.metadata, undefined, 4)}`);
|
2022-01-30 16:00:03 +00:00
|
|
|
|
2021-12-29 22:39:09 +00:00
|
|
|
await new Promise((promise) => setTimeout(promise, 5000));
|
2021-09-21 18:27:04 +00:00
|
|
|
CloudRunnerLogger.log('Job created');
|
2021-12-29 17:25:38 +00:00
|
|
|
this.setPodNameAndContainerName(await Kubernetes.findPodFromJob(this.kubeClient, this.jobName, this.namespace));
|
2021-09-21 18:27:04 +00:00
|
|
|
CloudRunnerLogger.log('Watching pod until running');
|
2021-12-30 21:00:38 +00:00
|
|
|
let output = '';
|
|
|
|
|
// eslint-disable-next-line no-constant-condition
|
|
|
|
|
while (true) {
|
|
|
|
|
try {
|
|
|
|
|
await KubernetesTaskRunner.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
|
|
|
|
|
CloudRunnerLogger.log('Pod running, streaming logs');
|
|
|
|
|
output = await KubernetesTaskRunner.runTask(
|
|
|
|
|
this.kubeConfig,
|
|
|
|
|
this.kubeClient,
|
|
|
|
|
this.jobName,
|
|
|
|
|
this.podName,
|
|
|
|
|
'main',
|
|
|
|
|
this.namespace,
|
|
|
|
|
CloudRunnerLogger.log,
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.message.includes(`HTTP`)) {
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-19 23:06:44 +00:00
|
|
|
await this.cleanupTaskResources();
|
2021-12-30 20:25:28 +00:00
|
|
|
return output;
|
2021-06-18 20:36:45 +00:00
|
|
|
} catch (error) {
|
2021-09-21 18:27:04 +00:00
|
|
|
CloudRunnerLogger.log('Running job failed');
|
2021-06-19 20:35:22 +00:00
|
|
|
core.error(JSON.stringify(error, undefined, 4));
|
2021-06-19 23:06:44 +00:00
|
|
|
await this.cleanupTaskResources();
|
2021-06-06 20:10:01 +00:00
|
|
|
throw error;
|
|
|
|
|
}
|
2021-05-23 14:26:57 +00:00
|
|
|
}
|
|
|
|
|
|
2021-06-06 21:22:22 +00:00
|
|
|
setPodNameAndContainerName(pod: k8s.V1Pod) {
|
|
|
|
|
this.podName = pod.metadata?.name || '';
|
|
|
|
|
this.containerName = pod.status?.containerStatuses?.[0].name || '';
|
2021-05-28 17:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
2021-06-19 23:06:44 +00:00
|
|
|
async cleanupTaskResources() {
|
2021-09-21 18:27:04 +00:00
|
|
|
CloudRunnerLogger.log('cleaning up');
|
2022-01-30 17:21:38 +00:00
|
|
|
try {
|
|
|
|
|
await this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
|
|
|
|
|
await this.kubeClient.deleteNamespacedPod(this.podName, this.namespace);
|
|
|
|
|
await this.kubeClient.deleteNamespacedSecret(this.secretName, this.namespace);
|
2022-01-30 21:11:13 +00:00
|
|
|
await new Promise((promise) => setTimeout(promise, 5000));
|
2022-01-30 17:21:38 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
CloudRunnerLogger.log('Failed to cleanup, error:');
|
|
|
|
|
core.error(JSON.stringify(error, undefined, 4));
|
|
|
|
|
CloudRunnerLogger.log('Abandoning cleanup, build error:');
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2021-06-26 04:01:43 +00:00
|
|
|
try {
|
|
|
|
|
await waitUntil(
|
2022-01-30 15:41:45 +00:00
|
|
|
async () => {
|
|
|
|
|
const jobBody = (await this.kubeClientBatch.readNamespacedJob(this.jobName, this.namespace)).body;
|
2022-01-30 18:47:54 +00:00
|
|
|
const podBody = (await this.kubeClient.readNamespacedPod(this.podName, this.namespace)).body;
|
|
|
|
|
return (jobBody === null || jobBody.status?.active === 0) && podBody === null;
|
2022-01-30 15:41:45 +00:00
|
|
|
},
|
2021-06-26 04:01:43 +00:00
|
|
|
{
|
|
|
|
|
timeout: 500000,
|
|
|
|
|
intervalBetweenAttempts: 15000,
|
|
|
|
|
},
|
|
|
|
|
);
|
2022-01-12 23:15:24 +00:00
|
|
|
// eslint-disable-next-line no-empty
|
|
|
|
|
} catch {}
|
2021-05-23 15:08:32 +00:00
|
|
|
}
|
2021-06-19 23:06:44 +00:00
|
|
|
|
2021-12-25 20:10:12 +00:00
|
|
|
async cleanupSharedResources(
|
2021-06-19 23:06:44 +00:00
|
|
|
// eslint-disable-next-line no-unused-vars
|
2021-08-15 21:59:58 +00:00
|
|
|
buildGuid: string,
|
2021-06-19 23:06:44 +00:00
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
|
buildParameters: BuildParameters,
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
|
branchName: string,
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
|
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
|
|
|
|
) {
|
2022-01-30 18:47:54 +00:00
|
|
|
CloudRunnerLogger.log(`deleting PVC`);
|
2022-01-31 21:03:44 +00:00
|
|
|
await this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
|
2022-01-30 21:09:58 +00:00
|
|
|
CloudRunnerLogger.log(`deleted PVC`);
|
2021-06-19 23:06:44 +00:00
|
|
|
}
|
2022-01-31 21:03:44 +00:00
|
|
|
|
2021-12-29 17:25:38 +00:00
|
|
|
static async findPodFromJob(kubeClient: CoreV1Api, jobName: string, namespace: string) {
|
|
|
|
|
const namespacedPods = await kubeClient.listNamespacedPod(namespace);
|
|
|
|
|
const pod = namespacedPods.body.items.find((x) => x.metadata?.labels?.['job-name'] === jobName);
|
|
|
|
|
if (pod === undefined) {
|
|
|
|
|
throw new Error("pod with job-name label doesn't exist");
|
|
|
|
|
}
|
|
|
|
|
return pod;
|
|
|
|
|
}
|
2020-08-09 19:27:47 +00:00
|
|
|
}
|
|
|
|
|
export default Kubernetes;
|