Fix: error handling

pull/273/head
Frostebite 2021-06-06 22:22:22 +01:00
parent 4208953d5a
commit d0402e14b1
3 changed files with 73 additions and 62 deletions

51
dist/index.js vendored
View File

@ -737,6 +737,10 @@ const base64 = __webpack_require__(85848);
const pollInterval = 20000; const pollInterval = 20000;
class Kubernetes { class Kubernetes {
constructor(buildParameters, baseImage) { constructor(buildParameters, baseImage) {
this.buildId = '';
this.pvcName = '';
this.secretName = '';
this.jobName = '';
this.podName = ''; this.podName = '';
this.containerName = ''; this.containerName = '';
const kc = new k8s.KubeConfig(); const kc = new k8s.KubeConfig();
@ -744,21 +748,23 @@ class Kubernetes {
const k8sApi = kc.makeApiClient(k8s.CoreV1Api); const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api); const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api);
core.info('Loaded default Kubernetes configuration for this environment'); core.info('Loaded default Kubernetes configuration for this environment');
this.kubeConfig = kc;
this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi;
this.namespace = 'default';
this.buildParameters = buildParameters;
this.baseImage = baseImage;
this.setUniqueBuildId();
}
setUniqueBuildId() {
const buildId = Kubernetes.uuidv4(); const buildId = Kubernetes.uuidv4();
const pvcName = `unity-builder-pvc-${buildId}`; const pvcName = `unity-builder-pvc-${buildId}`;
const secretName = `build-credentials-${buildId}`; const secretName = `build-credentials-${buildId}`;
const jobName = `unity-builder-job-${buildId}`; const jobName = `unity-builder-job-${buildId}`;
const namespace = 'default';
this.kubeConfig = kc;
this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi;
this.buildId = buildId; this.buildId = buildId;
this.pvcName = pvcName; this.pvcName = pvcName;
this.secretName = secretName; this.secretName = secretName;
this.jobName = jobName; this.jobName = jobName;
this.namespace = namespace;
this.buildParameters = buildParameters;
this.baseImage = baseImage;
} }
run() { run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
@ -799,7 +805,7 @@ class Kubernetes {
yield this.runBuildJob(); yield this.runBuildJob();
} }
catch (error) { catch (error) {
core.error(JSON.stringify(error, undefined, 4)); core.error(JSON.stringify(error.response.body, undefined, 4));
throw error; throw error;
} }
core.setOutput('volume', this.pvcName); core.setOutput('volume', this.pvcName);
@ -978,21 +984,21 @@ class Kubernetes {
job.spec.backoffLimit = 1; job.spec.backoffLimit = 1;
return job; return job;
} }
runJob(jobSpec) { runJob(command, image) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
this.setUniqueBuildId();
const jobSpec = this.getJobSpec(command, image);
core.info('Creating build job'); core.info('Creating build job');
yield this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec); yield this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
core.info('Job created'); core.info('Job created');
// We watch the PVC first to allow some time for K8s to notice the job we created and setup a pod. yield this.watchPersistentVolumeClaimUntilBoundToContainer();
yield this.watchPersistentVolumeClaimUntilReady();
// TODO: Wait for something more reliable so we don't potentially get the pod before k8s has created it based on the job.
this.setPodNameAndContainerName(yield this.getPod()); this.setPodNameAndContainerName(yield this.getPod());
yield this.watchUntilPodRunning(); yield this.watchUntilPodRunning();
yield this.streamLogs(); yield this.streamLogs();
} }
catch (error) { catch (error) {
core.error(JSON.stringify(error.response.body, undefined, 4)); throw error;
} }
finally { finally {
yield this.cleanup(); yield this.cleanup();
@ -1003,6 +1009,9 @@ class Kubernetes {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (this.podName === '') { if (this.podName === '') {
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; }); 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; });
if (pod === undefined) {
throw new Error("pod with job-name label doesn't exist");
}
return pod; return pod;
} }
else { else {
@ -1012,7 +1021,7 @@ class Kubernetes {
} }
runCloneJob() { runCloneJob() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield this.runJob(this.getJobSpec([ yield this.runJob([
'/bin/ash', '/bin/ash',
'-c', '-c',
`apk update; `apk update;
@ -1026,12 +1035,12 @@ class Kubernetes {
git checkout $GITHUB_SHA; git checkout $GITHUB_SHA;
ls ls
echo "end"`, echo "end"`,
], 'alpine/git')); ], 'alpine/git');
}); });
} }
runBuildJob() { runBuildJob() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield this.runJob(this.getJobSpec([ yield this.runJob([
'bin/bash', 'bin/bash',
'-c', '-c',
`ls `ls
@ -1046,16 +1055,16 @@ class Kubernetes {
chmod -R +x /steps chmod -R +x /steps
/entrypoint.sh /entrypoint.sh
`, `,
], this.baseImage.toString())); ], this.baseImage.toString());
}); });
} }
watchPersistentVolumeClaimUntilReady() { watchPersistentVolumeClaimUntilBoundToContainer() {
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 this.watchPersistentVolumeClaimUntilReady(); yield this.watchPersistentVolumeClaimUntilBoundToContainer();
} }
else { else {
core.info('Persistent Volume ready for claims'); core.info('Persistent Volume ready for claims');
@ -1086,8 +1095,8 @@ class Kubernetes {
} }
setPodNameAndContainerName(pod) { setPodNameAndContainerName(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.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.status) === null || _b === void 0 ? void 0 : _b.containerStatuses) === null || _c === void 0 ? void 0 : _c[0].name) || '';
} }
streamLogs() { streamLogs() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -13,12 +13,12 @@ class Kubernetes implements RemoteBuilderProviderInterface {
private kubeConfig: KubeConfig; private kubeConfig: KubeConfig;
private kubeClient: k8s.CoreV1Api; private kubeClient: k8s.CoreV1Api;
private kubeClientBatch: k8s.BatchV1Api; private kubeClientBatch: k8s.BatchV1Api;
private buildId: string; private buildId: string = '';
private buildParameters: BuildParameters; private buildParameters: BuildParameters;
private baseImage: any; private baseImage: any;
private pvcName: string; private pvcName: string = '';
private secretName: string; private secretName: string = '';
private jobName: string; private jobName: string = '';
private namespace: string; private namespace: string;
private podName: string = ''; private podName: string = '';
private containerName: string = ''; private containerName: string = '';
@ -30,22 +30,27 @@ class Kubernetes implements RemoteBuilderProviderInterface {
const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api); const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api);
core.info('Loaded default Kubernetes configuration for this environment'); core.info('Loaded default Kubernetes configuration for this environment');
this.kubeConfig = kc;
this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi;
this.namespace = 'default';
this.buildParameters = buildParameters;
this.baseImage = baseImage;
this.setUniqueBuildId();
}
setUniqueBuildId() {
const buildId = Kubernetes.uuidv4(); const buildId = Kubernetes.uuidv4();
const pvcName = `unity-builder-pvc-${buildId}`; const pvcName = `unity-builder-pvc-${buildId}`;
const secretName = `build-credentials-${buildId}`; const secretName = `build-credentials-${buildId}`;
const jobName = `unity-builder-job-${buildId}`; const jobName = `unity-builder-job-${buildId}`;
const namespace = 'default';
this.kubeConfig = kc;
this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi;
this.buildId = buildId; this.buildId = buildId;
this.pvcName = pvcName; this.pvcName = pvcName;
this.secretName = secretName; this.secretName = secretName;
this.jobName = jobName; this.jobName = jobName;
this.namespace = namespace;
this.buildParameters = buildParameters;
this.baseImage = baseImage;
} }
async run() { async run() {
@ -85,7 +90,7 @@ class Kubernetes implements RemoteBuilderProviderInterface {
await this.runCloneJob(); await this.runCloneJob();
await this.runBuildJob(); await this.runBuildJob();
} catch (error) { } catch (error) {
core.error(JSON.stringify(error, undefined, 4)); core.error(JSON.stringify(error.response.body, undefined, 4));
throw error; throw error;
} }
@ -263,21 +268,19 @@ class Kubernetes implements RemoteBuilderProviderInterface {
return job; return job;
} }
async runJob(jobSpec: k8s.V1Job) { async runJob(command: string[], image: string) {
try { try {
this.setUniqueBuildId();
const jobSpec = this.getJobSpec(command, image);
core.info('Creating build job'); core.info('Creating build job');
await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec); await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
core.info('Job created'); core.info('Job created');
// We watch the PVC first to allow some time for K8s to notice the job we created and setup a pod. await this.watchPersistentVolumeClaimUntilBoundToContainer();
await this.watchPersistentVolumeClaimUntilReady();
// TODO: Wait for something more reliable so we don't potentially get the pod before k8s has created it based on the job.
this.setPodNameAndContainerName(await this.getPod()); this.setPodNameAndContainerName(await this.getPod());
await this.watchUntilPodRunning(); await this.watchUntilPodRunning();
await this.streamLogs(); await this.streamLogs();
} catch (error) { } catch (error) {
core.error(JSON.stringify(error.response.body, undefined, 4)); throw error;
} finally { } finally {
await this.cleanup(); await this.cleanup();
} }
@ -288,6 +291,9 @@ class Kubernetes implements RemoteBuilderProviderInterface {
const pod = (await this.kubeClient.listNamespacedPod(this.namespace)).body.items.find( const pod = (await this.kubeClient.listNamespacedPod(this.namespace)).body.items.find(
(x) => x.metadata?.labels?.['job-name'] === this.jobName, (x) => x.metadata?.labels?.['job-name'] === this.jobName,
); );
if (pod === undefined) {
throw new Error("pod with job-name label doesn't exist");
}
return pod; return pod;
} else { } else {
return (await this.kubeClient.readNamespacedPod(this.podName, this.namespace)).body; return (await this.kubeClient.readNamespacedPod(this.podName, this.namespace)).body;
@ -296,11 +302,10 @@ class Kubernetes implements RemoteBuilderProviderInterface {
async runCloneJob() { async runCloneJob() {
await this.runJob( await this.runJob(
this.getJobSpec( [
[ '/bin/ash',
'/bin/ash', '-c',
'-c', `apk update;
`apk update;
apk add git-lfs; apk add git-lfs;
ls /credentials/ ls /credentials/
export GITHUB_TOKEN=$(cat /credentials/GITHUB_TOKEN); export GITHUB_TOKEN=$(cat /credentials/GITHUB_TOKEN);
@ -311,19 +316,17 @@ class Kubernetes implements RemoteBuilderProviderInterface {
git checkout $GITHUB_SHA; git checkout $GITHUB_SHA;
ls ls
echo "end"`, echo "end"`,
], ],
'alpine/git', 'alpine/git',
),
); );
} }
async runBuildJob() { async runBuildJob() {
await this.runJob( await this.runJob(
this.getJobSpec( [
[ 'bin/bash',
'bin/bash', '-c',
'-c', `ls
`ls
for f in ./credentials/*; do export $(basename $f)="$(cat $f)"; done for f in ./credentials/*; do export $(basename $f)="$(cat $f)"; done
ls /data ls /data
ls /data/builder ls /data/builder
@ -335,18 +338,17 @@ class Kubernetes implements RemoteBuilderProviderInterface {
chmod -R +x /steps chmod -R +x /steps
/entrypoint.sh /entrypoint.sh
`, `,
], ],
this.baseImage.toString(), this.baseImage.toString(),
),
); );
} }
async watchPersistentVolumeClaimUntilReady() { async watchPersistentVolumeClaimUntilBoundToContainer() {
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 this.watchPersistentVolumeClaimUntilReady(); await this.watchPersistentVolumeClaimUntilBoundToContainer();
} else { } else {
core.info('Persistent Volume ready for claims'); core.info('Persistent Volume ready for claims');
} }
@ -373,9 +375,9 @@ class Kubernetes implements RemoteBuilderProviderInterface {
} }
} }
setPodNameAndContainerName(pod: k8s.V1Pod | any) { setPodNameAndContainerName(pod: k8s.V1Pod) {
this.podName = pod?.metadata?.name || ''; this.podName = pod.metadata?.name || '';
this.containerName = pod?.status?.containerStatuses?.[0].name || ''; this.containerName = pod.status?.containerStatuses?.[0].name || '';
} }
async streamLogs() { async streamLogs() {