Refactor, init k8s pvc build id separately
parent
5a7ed829ac
commit
d3a99ff1b3
|
|
@ -858,7 +858,7 @@ class AWSBuildEnvironment {
|
|||
constructor(buildParameters) {
|
||||
this.stackName = buildParameters.awsStackName;
|
||||
}
|
||||
CleanupSharedBuildResources(
|
||||
cleanupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
@ -869,7 +869,7 @@ class AWSBuildEnvironment {
|
|||
defaultSecretsArray) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
SetupSharedBuildResources(
|
||||
setupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
@ -1276,11 +1276,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const k8s = __importStar(__webpack_require__(89679));
|
||||
const core = __importStar(__webpack_require__(42186));
|
||||
const client_node_1 = __webpack_require__(89679);
|
||||
const stream_1 = __webpack_require__(92413);
|
||||
const async_wait_until_1 = __webpack_require__(41299);
|
||||
const kubernetes_storage_1 = __importDefault(__webpack_require__(38941));
|
||||
const base64 = __webpack_require__(85848);
|
||||
const kubernetes_logging_1 = __importDefault(__webpack_require__(37290));
|
||||
const kubernetes_secret_1 = __importDefault(__webpack_require__(35129));
|
||||
const kubernetes_utils_1 = __importDefault(__webpack_require__(92040));
|
||||
class Kubernetes {
|
||||
constructor(buildParameters) {
|
||||
this.buildId = '';
|
||||
|
|
@ -1300,84 +1299,44 @@ class Kubernetes {
|
|||
this.namespace = 'default';
|
||||
this.buildParameters = buildParameters;
|
||||
}
|
||||
CleanupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
|
||||
});
|
||||
}
|
||||
SetupSharedBuildResources(buildUid, buildParameters,
|
||||
setupSharedBuildResources(buildUid, buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const pvcName = `unity-builder-pvc-${buildUid}`;
|
||||
this.pvcName = pvcName;
|
||||
yield kubernetes_storage_1.default.createPersistentVolumeClaim(buildParameters, this.pvcName, this.kubeClient, this.namespace);
|
||||
});
|
||||
}
|
||||
runBuildTask(buildId, image, commands, mountdir, workingdir, environment, secrets) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
this.setUniqueBuildId(buildId);
|
||||
// setup
|
||||
yield this.createSecret(secrets);
|
||||
this.buildId = buildId;
|
||||
this.secretName = `build-credentials-${buildId}`;
|
||||
this.jobName = `unity-builder-job-${buildId}`;
|
||||
yield kubernetes_secret_1.default.createSecret(secrets, this.secretName, this.namespace, this.kubeClient);
|
||||
yield kubernetes_storage_1.default.createPersistentVolumeClaim(this.buildParameters, this.pvcName, this.kubeClient, this.namespace);
|
||||
//run
|
||||
const jobSpec = this.getJobSpec(commands, image, mountdir, workingdir, environment);
|
||||
//run
|
||||
core.info('Creating build job');
|
||||
yield this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
|
||||
core.info('Job created');
|
||||
yield kubernetes_storage_1.default.watchUntilPVCNotPending(this.kubeClient, this.pvcName, this.namespace);
|
||||
core.info('PVC Bound');
|
||||
this.setPodNameAndContainerName(yield this.findPod());
|
||||
this.setPodNameAndContainerName(yield kubernetes_utils_1.default.findPodFromJob(this.kubeClient, this.jobName, this.namespace));
|
||||
core.info('Watching pod until running');
|
||||
yield this.watchUntilPodRunning();
|
||||
yield kubernetes_utils_1.default.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
|
||||
core.info('Pod running, streaming logs');
|
||||
yield this.streamLogs();
|
||||
yield this.cleanup();
|
||||
yield kubernetes_logging_1.default.streamLogs(this.kubeConfig, this.kubeClient, this.jobName, this.podName, this.containerName, this.namespace, core.info);
|
||||
yield this.cleanupTaskResources();
|
||||
}
|
||||
catch (error) {
|
||||
core.info('Running job failed');
|
||||
core.error(JSON.stringify(error, undefined, 4));
|
||||
yield this.cleanup();
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
setUniqueBuildId(buildId) {
|
||||
const pvcName = `unity-builder-pvc-${buildId}`;
|
||||
const secretName = `build-credentials-${buildId}`;
|
||||
const jobName = `unity-builder-job-${buildId}`;
|
||||
this.buildId = buildId;
|
||||
this.pvcName = pvcName;
|
||||
this.secretName = secretName;
|
||||
this.jobName = jobName;
|
||||
}
|
||||
createSecret(secrets) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const secret = new k8s.V1Secret();
|
||||
secret.apiVersion = 'v1';
|
||||
secret.kind = 'Secret';
|
||||
secret.type = 'Opaque';
|
||||
secret.metadata = {
|
||||
name: this.secretName,
|
||||
};
|
||||
secret.data = {};
|
||||
for (const buildSecret of secrets) {
|
||||
secret.data[buildSecret.EnvironmentVariable] = base64.encode(buildSecret.ParameterValue);
|
||||
secret.data[`${buildSecret.EnvironmentVariable}_NAME`] = base64.encode(buildSecret.ParameterKey);
|
||||
}
|
||||
try {
|
||||
yield this.kubeClient.createNamespacedSecret(this.namespace, secret);
|
||||
}
|
||||
catch (error) {
|
||||
yield this.cleanupTaskResources();
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
|
@ -1509,46 +1468,90 @@ class Kubernetes {
|
|||
};
|
||||
return job;
|
||||
}
|
||||
findPod() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
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;
|
||||
});
|
||||
}
|
||||
watchUntilPodRunning() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let success = false;
|
||||
core.info(`Watching ${this.podName} ${this.namespace}`);
|
||||
yield async_wait_until_1.waitUntil(() => __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b;
|
||||
const phase = (_b = (_a = (yield this.kubeClient.readNamespacedPodStatus(this.podName, this.namespace))) === null || _a === void 0 ? void 0 : _a.body.status) === null || _b === void 0 ? void 0 : _b.phase;
|
||||
success = phase === 'Running';
|
||||
if (success || phase !== 'Pending')
|
||||
return true;
|
||||
return false;
|
||||
}), {
|
||||
timeout: 500000,
|
||||
intervalBetweenAttempts: 15000,
|
||||
});
|
||||
return success;
|
||||
});
|
||||
}
|
||||
setPodNameAndContainerName(pod) {
|
||||
var _a, _b, _c;
|
||||
this.podName = ((_a = pod.metadata) === null || _a === void 0 ? void 0 : _a.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() {
|
||||
cleanupTaskResources() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.info(`Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`);
|
||||
core.info('cleaning up');
|
||||
try {
|
||||
yield this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
|
||||
yield this.kubeClient.deleteNamespacedSecret(this.secretName, this.namespace);
|
||||
}
|
||||
catch (error) {
|
||||
core.info('Failed to cleanup, error:');
|
||||
core.error(JSON.stringify(error, undefined, 4));
|
||||
core.info('Abandoning cleanup, build error:');
|
||||
}
|
||||
});
|
||||
}
|
||||
cleanupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.default = Kubernetes;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 37290:
|
||||
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const client_node_1 = __webpack_require__(89679);
|
||||
const stream_1 = __webpack_require__(92413);
|
||||
const core = __importStar(__webpack_require__(42186));
|
||||
class KubernetesLogging {
|
||||
static streamLogs(kubeConfig, kubeClient, jobName, podName, containerName, namespace, logCallback) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.info(`Streaming logs from pod: ${podName} container: ${containerName} namespace: ${namespace}`);
|
||||
const stream = new stream_1.Writable();
|
||||
let didStreamAnyLogs = false;
|
||||
stream._write = (chunk, encoding, next) => {
|
||||
didStreamAnyLogs = true;
|
||||
core.info(chunk.toString());
|
||||
logCallback(chunk.toString());
|
||||
next();
|
||||
};
|
||||
const logOptions = {
|
||||
|
|
@ -1557,16 +1560,16 @@ class Kubernetes {
|
|||
previous: false,
|
||||
};
|
||||
try {
|
||||
const resultError = yield new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { return new client_node_1.Log(this.kubeConfig).log(this.namespace, this.podName, this.containerName, stream, resolve, logOptions); }));
|
||||
const resultError = yield new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { return new client_node_1.Log(kubeConfig).log(namespace, podName, containerName, stream, resolve, logOptions); }));
|
||||
if (resultError) {
|
||||
throw resultError;
|
||||
}
|
||||
if (!didStreamAnyLogs) {
|
||||
throw new Error(JSON.stringify({
|
||||
message: 'Failed to stream any logs, listing namespace events, check for an error with the container',
|
||||
events: (yield this.kubeClient.listNamespacedEvent(this.namespace)).body.items
|
||||
events: (yield kubeClient.listNamespacedEvent(namespace)).body.items
|
||||
.filter((x) => {
|
||||
return x.involvedObject.name === this.podName || x.involvedObject.name === this.jobName;
|
||||
return x.involvedObject.name === podName || x.involvedObject.name === jobName;
|
||||
})
|
||||
.map((x) => {
|
||||
return {
|
||||
|
|
@ -1584,22 +1587,73 @@ class Kubernetes {
|
|||
core.info('end of log stream');
|
||||
});
|
||||
}
|
||||
cleanup() {
|
||||
}
|
||||
exports.default = KubernetesLogging;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 35129:
|
||||
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const k8s = __importStar(__webpack_require__(89679));
|
||||
const base64 = __webpack_require__(85848);
|
||||
class KubernetesSecret {
|
||||
static createSecret(secrets, secretName, namespace, kubeClient) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.info('cleaning up');
|
||||
const secret = new k8s.V1Secret();
|
||||
secret.apiVersion = 'v1';
|
||||
secret.kind = 'Secret';
|
||||
secret.type = 'Opaque';
|
||||
secret.metadata = {
|
||||
name: secretName,
|
||||
};
|
||||
secret.data = {};
|
||||
for (const buildSecret of secrets) {
|
||||
secret.data[buildSecret.EnvironmentVariable] = base64.encode(buildSecret.ParameterValue);
|
||||
secret.data[`${buildSecret.EnvironmentVariable}_NAME`] = base64.encode(buildSecret.ParameterKey);
|
||||
}
|
||||
try {
|
||||
yield this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
|
||||
yield this.kubeClient.deleteNamespacedSecret(this.secretName, this.namespace);
|
||||
yield kubeClient.createNamespacedSecret(namespace, secret);
|
||||
}
|
||||
catch (error) {
|
||||
core.info('Failed to cleanup, error:');
|
||||
core.error(JSON.stringify(error, undefined, 4));
|
||||
core.info('Abandoning cleanup, build error:');
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.default = Kubernetes;
|
||||
exports.default = KubernetesSecret;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
|
@ -1692,6 +1746,79 @@ class KubernetesStorage {
|
|||
exports.default = KubernetesStorage;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 92040:
|
||||
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const async_wait_until_1 = __importDefault(__webpack_require__(41299));
|
||||
const core = __importStar(__webpack_require__(42186));
|
||||
class KubernetesUtilities {
|
||||
static findPodFromJob(kubeClient, jobName, namespace) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const pod = (yield kubeClient.listNamespacedPod(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']) === jobName; });
|
||||
if (pod === undefined) {
|
||||
throw new Error("pod with job-name label doesn't exist");
|
||||
}
|
||||
return pod;
|
||||
});
|
||||
}
|
||||
static watchUntilPodRunning(kubeClient, podName, namespace) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let success = false;
|
||||
core.info(`Watching ${podName} ${namespace}`);
|
||||
yield async_wait_until_1.default(() => __awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b;
|
||||
const phase = (_b = (_a = (yield kubeClient.readNamespacedPodStatus(podName, namespace))) === null || _a === void 0 ? void 0 : _a.body.status) === null || _b === void 0 ? void 0 : _b.phase;
|
||||
success = phase === 'Running';
|
||||
if (success || phase !== 'Pending')
|
||||
return true;
|
||||
return false;
|
||||
}), {
|
||||
timeout: 500000,
|
||||
intervalBetweenAttempts: 15000,
|
||||
});
|
||||
return success;
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.default = KubernetesUtilities;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 92560:
|
||||
|
|
@ -1809,7 +1936,7 @@ class RemoteBuilder {
|
|||
this.RemoteBuilderProviderPlatform = new kubernetes_build_platform_1.default(buildParameters);
|
||||
break;
|
||||
}
|
||||
yield this.RemoteBuilderProviderPlatform.SetupSharedBuildResources(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
yield this.RemoteBuilderProviderPlatform.setupSharedBuildResources(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
yield RemoteBuilder.SetupStep(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
yield RemoteBuilder.BuildStep(buildUid, buildParameters, baseImage, defaultSecretsArray);
|
||||
yield RemoteBuilder.CompressionStep(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
|
|
@ -1817,12 +1944,12 @@ class RemoteBuilder {
|
|||
if (this.SteamDeploy) {
|
||||
yield RemoteBuilder.DeployToSteam(buildUid, buildParameters, defaultSecretsArray);
|
||||
}
|
||||
yield this.RemoteBuilderProviderPlatform.CleanupSharedBuildResources(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
yield this.RemoteBuilderProviderPlatform.cleanupSharedBuildResources(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error);
|
||||
core.error(error);
|
||||
yield this.RemoteBuilderProviderPlatform.CleanupSharedBuildResources(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
core.error(JSON.stringify(error, undefined, 4));
|
||||
core.setFailed('Remote Builder failed');
|
||||
yield this.RemoteBuilderProviderPlatform.cleanupSharedBuildResources(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -16,7 +16,7 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
|
|||
constructor(buildParameters: BuildParameters) {
|
||||
this.stackName = buildParameters.awsStackName;
|
||||
}
|
||||
CleanupSharedBuildResources(
|
||||
cleanupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid: string,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
@ -28,7 +28,7 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
|
|||
) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
SetupSharedBuildResources(
|
||||
setupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid: string,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import * as k8s from '@kubernetes/client-node';
|
||||
import { BuildParameters } from '..';
|
||||
import * as core from '@actions/core';
|
||||
import { KubeConfig, Log } from '@kubernetes/client-node';
|
||||
import { Writable } from 'stream';
|
||||
import { RemoteBuilderProviderInterface } from './remote-builder-provider-interface';
|
||||
import RemoteBuilderSecret from './remote-builder-secret';
|
||||
import { waitUntil } from 'async-wait-until';
|
||||
import KubernetesStorage from './kubernetes-storage';
|
||||
import RemoteBuilderEnvironmentVariable from './remote-builder-environment-variable';
|
||||
import KubernetesLogging from './kubernetes-logging';
|
||||
import KubernetesSecret from './kubernetes-secret';
|
||||
import KubernetesUtilities from './kubernetes-utils';
|
||||
|
||||
const base64 = require('base-64');
|
||||
class Kubernetes implements RemoteBuilderProviderInterface {
|
||||
private kubeConfig: KubeConfig;
|
||||
private kubeConfig: k8s.KubeConfig;
|
||||
private kubeClient: k8s.CoreV1Api;
|
||||
private kubeClientBatch: k8s.BatchV1Api;
|
||||
private buildId: string = '';
|
||||
|
|
@ -37,19 +36,7 @@ class Kubernetes implements RemoteBuilderProviderInterface {
|
|||
this.namespace = 'default';
|
||||
this.buildParameters = buildParameters;
|
||||
}
|
||||
async CleanupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid: string,
|
||||
// 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 }[],
|
||||
) {
|
||||
await this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
|
||||
}
|
||||
public async SetupSharedBuildResources(
|
||||
public async setupSharedBuildResources(
|
||||
buildUid: string,
|
||||
buildParameters: BuildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
@ -57,6 +44,8 @@ class Kubernetes implements RemoteBuilderProviderInterface {
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
||||
) {
|
||||
const pvcName = `unity-builder-pvc-${buildUid}`;
|
||||
this.pvcName = pvcName;
|
||||
await KubernetesStorage.createPersistentVolumeClaim(buildParameters, this.pvcName, this.kubeClient, this.namespace);
|
||||
}
|
||||
|
||||
|
|
@ -70,64 +59,45 @@ class Kubernetes implements RemoteBuilderProviderInterface {
|
|||
secrets: RemoteBuilderSecret[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.setUniqueBuildId(buildId);
|
||||
// setup
|
||||
await this.createSecret(secrets);
|
||||
this.buildId = buildId;
|
||||
this.secretName = `build-credentials-${buildId}`;
|
||||
this.jobName = `unity-builder-job-${buildId}`;
|
||||
await KubernetesSecret.createSecret(secrets, this.secretName, this.namespace, this.kubeClient);
|
||||
await KubernetesStorage.createPersistentVolumeClaim(
|
||||
this.buildParameters,
|
||||
this.pvcName,
|
||||
this.kubeClient,
|
||||
this.namespace,
|
||||
);
|
||||
const jobSpec = this.getJobSpec(commands, image, mountdir, workingdir, environment);
|
||||
|
||||
//run
|
||||
const jobSpec = this.getJobSpec(commands, image, mountdir, workingdir, environment);
|
||||
core.info('Creating build job');
|
||||
await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
|
||||
core.info('Job created');
|
||||
await KubernetesStorage.watchUntilPVCNotPending(this.kubeClient, this.pvcName, this.namespace);
|
||||
core.info('PVC Bound');
|
||||
this.setPodNameAndContainerName(await this.findPod());
|
||||
this.setPodNameAndContainerName(
|
||||
await KubernetesUtilities.findPodFromJob(this.kubeClient, this.jobName, this.namespace),
|
||||
);
|
||||
core.info('Watching pod until running');
|
||||
await this.watchUntilPodRunning();
|
||||
await KubernetesUtilities.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
|
||||
core.info('Pod running, streaming logs');
|
||||
await this.streamLogs();
|
||||
await this.cleanup();
|
||||
await KubernetesLogging.streamLogs(
|
||||
this.kubeConfig,
|
||||
this.kubeClient,
|
||||
this.jobName,
|
||||
this.podName,
|
||||
this.containerName,
|
||||
this.namespace,
|
||||
core.info,
|
||||
);
|
||||
await this.cleanupTaskResources();
|
||||
} catch (error) {
|
||||
core.info('Running job failed');
|
||||
core.error(JSON.stringify(error, undefined, 4));
|
||||
await this.cleanup();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
setUniqueBuildId(buildId: string) {
|
||||
const pvcName = `unity-builder-pvc-${buildId}`;
|
||||
const secretName = `build-credentials-${buildId}`;
|
||||
const jobName = `unity-builder-job-${buildId}`;
|
||||
|
||||
this.buildId = buildId;
|
||||
this.pvcName = pvcName;
|
||||
this.secretName = secretName;
|
||||
this.jobName = jobName;
|
||||
}
|
||||
|
||||
async createSecret(secrets: RemoteBuilderSecret[]) {
|
||||
const secret = new k8s.V1Secret();
|
||||
secret.apiVersion = 'v1';
|
||||
secret.kind = 'Secret';
|
||||
secret.type = 'Opaque';
|
||||
secret.metadata = {
|
||||
name: this.secretName,
|
||||
};
|
||||
secret.data = {};
|
||||
for (const buildSecret of secrets) {
|
||||
secret.data[buildSecret.EnvironmentVariable] = base64.encode(buildSecret.ParameterValue);
|
||||
secret.data[`${buildSecret.EnvironmentVariable}_NAME`] = base64.encode(buildSecret.ParameterKey);
|
||||
}
|
||||
try {
|
||||
await this.kubeClient.createNamespacedSecret(this.namespace, secret);
|
||||
} catch (error) {
|
||||
await this.cleanupTaskResources();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -269,89 +239,12 @@ class Kubernetes implements RemoteBuilderProviderInterface {
|
|||
return job;
|
||||
}
|
||||
|
||||
async findPod() {
|
||||
const pod = (await this.kubeClient.listNamespacedPod(this.namespace)).body.items.find(
|
||||
(x) => x.metadata?.labels?.['job-name'] === this.jobName,
|
||||
);
|
||||
if (pod === undefined) {
|
||||
throw new Error("pod with job-name label doesn't exist");
|
||||
}
|
||||
return pod;
|
||||
}
|
||||
|
||||
async watchUntilPodRunning() {
|
||||
let success: boolean = false;
|
||||
core.info(`Watching ${this.podName} ${this.namespace}`);
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const phase = (await this.kubeClient.readNamespacedPodStatus(this.podName, this.namespace))?.body.status?.phase;
|
||||
success = phase === 'Running';
|
||||
if (success || phase !== 'Pending') return true;
|
||||
return false;
|
||||
},
|
||||
{
|
||||
timeout: 500000,
|
||||
intervalBetweenAttempts: 15000,
|
||||
},
|
||||
);
|
||||
return success;
|
||||
}
|
||||
|
||||
setPodNameAndContainerName(pod: k8s.V1Pod) {
|
||||
this.podName = pod.metadata?.name || '';
|
||||
this.containerName = pod.status?.containerStatuses?.[0].name || '';
|
||||
}
|
||||
|
||||
async streamLogs() {
|
||||
core.info(`Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`);
|
||||
const stream = new Writable();
|
||||
let didStreamAnyLogs: boolean = false;
|
||||
stream._write = (chunk, encoding, next) => {
|
||||
didStreamAnyLogs = true;
|
||||
core.info(chunk.toString());
|
||||
next();
|
||||
};
|
||||
const logOptions = {
|
||||
follow: true,
|
||||
pretty: true,
|
||||
previous: false,
|
||||
};
|
||||
try {
|
||||
const resultError = await new Promise(async (resolve) =>
|
||||
new Log(this.kubeConfig).log(this.namespace, this.podName, this.containerName, stream, resolve, logOptions),
|
||||
);
|
||||
if (resultError) {
|
||||
throw resultError;
|
||||
}
|
||||
if (!didStreamAnyLogs) {
|
||||
throw new Error(
|
||||
JSON.stringify(
|
||||
{
|
||||
message: 'Failed to stream any logs, listing namespace events, check for an error with the container',
|
||||
events: (await this.kubeClient.listNamespacedEvent(this.namespace)).body.items
|
||||
.filter((x) => {
|
||||
return x.involvedObject.name === this.podName || x.involvedObject.name === this.jobName;
|
||||
})
|
||||
.map((x) => {
|
||||
return {
|
||||
type: x.involvedObject.kind,
|
||||
name: x.involvedObject.name,
|
||||
message: x.message,
|
||||
};
|
||||
}),
|
||||
},
|
||||
undefined,
|
||||
4,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
core.info('end of log stream');
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
async cleanupTaskResources() {
|
||||
core.info('cleaning up');
|
||||
try {
|
||||
await this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
|
||||
|
|
@ -362,5 +255,18 @@ class Kubernetes implements RemoteBuilderProviderInterface {
|
|||
core.info('Abandoning cleanup, build error:');
|
||||
}
|
||||
}
|
||||
|
||||
async cleanupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid: string,
|
||||
// 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 }[],
|
||||
) {
|
||||
await this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
|
||||
}
|
||||
}
|
||||
export default Kubernetes;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { CoreV1Api, KubeConfig, Log } from '@kubernetes/client-node';
|
||||
import { Writable } from 'stream';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
class KubernetesLogging {
|
||||
static async streamLogs(
|
||||
kubeConfig: KubeConfig,
|
||||
kubeClient: CoreV1Api,
|
||||
jobName: string,
|
||||
podName: string,
|
||||
containerName: string,
|
||||
namespace: string,
|
||||
logCallback: any,
|
||||
) {
|
||||
core.info(`Streaming logs from pod: ${podName} container: ${containerName} namespace: ${namespace}`);
|
||||
const stream = new Writable();
|
||||
let didStreamAnyLogs: boolean = false;
|
||||
stream._write = (chunk, encoding, next) => {
|
||||
didStreamAnyLogs = true;
|
||||
logCallback(chunk.toString());
|
||||
next();
|
||||
};
|
||||
const logOptions = {
|
||||
follow: true,
|
||||
pretty: true,
|
||||
previous: false,
|
||||
};
|
||||
try {
|
||||
const resultError = await new Promise(async (resolve) =>
|
||||
new Log(kubeConfig).log(namespace, podName, containerName, stream, resolve, logOptions),
|
||||
);
|
||||
if (resultError) {
|
||||
throw resultError;
|
||||
}
|
||||
if (!didStreamAnyLogs) {
|
||||
throw new Error(
|
||||
JSON.stringify(
|
||||
{
|
||||
message: 'Failed to stream any logs, listing namespace events, check for an error with the container',
|
||||
events: (await kubeClient.listNamespacedEvent(namespace)).body.items
|
||||
.filter((x) => {
|
||||
return x.involvedObject.name === podName || x.involvedObject.name === jobName;
|
||||
})
|
||||
.map((x) => {
|
||||
return {
|
||||
type: x.involvedObject.kind,
|
||||
name: x.involvedObject.name,
|
||||
message: x.message,
|
||||
};
|
||||
}),
|
||||
},
|
||||
undefined,
|
||||
4,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
core.info('end of log stream');
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesLogging;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { CoreV1Api } from '@kubernetes/client-node';
|
||||
import RemoteBuilderSecret from './remote-builder-secret';
|
||||
import * as k8s from '@kubernetes/client-node';
|
||||
const base64 = require('base-64');
|
||||
|
||||
class KubernetesSecret {
|
||||
static async createSecret(
|
||||
secrets: RemoteBuilderSecret[],
|
||||
secretName: string,
|
||||
namespace: string,
|
||||
kubeClient: CoreV1Api,
|
||||
) {
|
||||
const secret = new k8s.V1Secret();
|
||||
secret.apiVersion = 'v1';
|
||||
secret.kind = 'Secret';
|
||||
secret.type = 'Opaque';
|
||||
secret.metadata = {
|
||||
name: secretName,
|
||||
};
|
||||
secret.data = {};
|
||||
for (const buildSecret of secrets) {
|
||||
secret.data[buildSecret.EnvironmentVariable] = base64.encode(buildSecret.ParameterValue);
|
||||
secret.data[`${buildSecret.EnvironmentVariable}_NAME`] = base64.encode(buildSecret.ParameterKey);
|
||||
}
|
||||
try {
|
||||
await kubeClient.createNamespacedSecret(namespace, secret);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesSecret;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { CoreV1Api } from '@kubernetes/client-node';
|
||||
import waitUntil from 'async-wait-until';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
class KubernetesUtilities {
|
||||
static async findPodFromJob(kubeClient: CoreV1Api, jobName: string, namespace: string) {
|
||||
const pod = (await kubeClient.listNamespacedPod(namespace)).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;
|
||||
}
|
||||
|
||||
static async watchUntilPodRunning(kubeClient: CoreV1Api, podName: string, namespace: string) {
|
||||
let success: boolean = false;
|
||||
core.info(`Watching ${podName} ${namespace}`);
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const phase = (await kubeClient.readNamespacedPodStatus(podName, namespace))?.body.status?.phase;
|
||||
success = phase === 'Running';
|
||||
if (success || phase !== 'Pending') return true;
|
||||
return false;
|
||||
},
|
||||
{
|
||||
timeout: 500000,
|
||||
intervalBetweenAttempts: 15000,
|
||||
},
|
||||
);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
export default KubernetesUtilities;
|
||||
|
|
@ -3,7 +3,7 @@ import RemoteBuilderEnvironmentVariable from './remote-builder-environment-varia
|
|||
import RemoteBuilderSecret from './remote-builder-secret';
|
||||
|
||||
export interface RemoteBuilderProviderInterface {
|
||||
CleanupSharedBuildResources(
|
||||
cleanupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid: string,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
@ -13,7 +13,7 @@ export interface RemoteBuilderProviderInterface {
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
||||
);
|
||||
SetupSharedBuildResources(
|
||||
setupSharedBuildResources(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildUid: string,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class RemoteBuilder {
|
|||
this.RemoteBuilderProviderPlatform = new Kubernetes(buildParameters);
|
||||
break;
|
||||
}
|
||||
await this.RemoteBuilderProviderPlatform.SetupSharedBuildResources(
|
||||
await this.RemoteBuilderProviderPlatform.setupSharedBuildResources(
|
||||
buildUid,
|
||||
buildParameters,
|
||||
branchName,
|
||||
|
|
@ -61,16 +61,16 @@ class RemoteBuilder {
|
|||
if (this.SteamDeploy) {
|
||||
await RemoteBuilder.DeployToSteam(buildUid, buildParameters, defaultSecretsArray);
|
||||
}
|
||||
await this.RemoteBuilderProviderPlatform.CleanupSharedBuildResources(
|
||||
await this.RemoteBuilderProviderPlatform.cleanupSharedBuildResources(
|
||||
buildUid,
|
||||
buildParameters,
|
||||
branchName,
|
||||
defaultSecretsArray,
|
||||
);
|
||||
} catch (error) {
|
||||
core.setFailed(error);
|
||||
core.error(error);
|
||||
await this.RemoteBuilderProviderPlatform.CleanupSharedBuildResources(
|
||||
core.error(JSON.stringify(error, undefined, 4));
|
||||
core.setFailed('Remote Builder failed');
|
||||
await this.RemoteBuilderProviderPlatform.cleanupSharedBuildResources(
|
||||
buildUid,
|
||||
buildParameters,
|
||||
branchName,
|
||||
|
|
|
|||
Loading…
Reference in New Issue