PVC handling refactoring and logging

pull/273/head
Frostebite 2021-06-18 20:52:07 +01:00
parent 09ed77c94a
commit 08db6c5022
5 changed files with 564 additions and 487 deletions

945
dist/index.js vendored
View File

@ -580,8 +580,8 @@ const unity_1 = __importDefault(__webpack_require__(70498));
exports.Unity = unity_1.default; exports.Unity = unity_1.default;
const versioning_1 = __importDefault(__webpack_require__(88729)); const versioning_1 = __importDefault(__webpack_require__(88729));
exports.Versioning = versioning_1.default; exports.Versioning = versioning_1.default;
const kubernetes_1 = __importDefault(__webpack_require__(7352)); const kubernetes_build_platform_1 = __importDefault(__webpack_require__(81730));
exports.Kubernetes = kubernetes_1.default; exports.Kubernetes = kubernetes_build_platform_1.default;
const remote_builder_1 = __importDefault(__webpack_require__(49358)); const remote_builder_1 = __importDefault(__webpack_require__(49358));
exports.RemoteBuilder = remote_builder_1.default; exports.RemoteBuilder = remote_builder_1.default;
@ -693,450 +693,6 @@ class Input {
exports.default = Input; exports.default = Input;
/***/ }),
/***/ 7352:
/***/ (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 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 base64 = __webpack_require__(85848);
class Kubernetes {
constructor(buildParameters, baseImage) {
this.buildId = '';
this.pvcName = '';
this.secretName = '';
this.jobName = '';
this.podName = '';
this.containerName = '';
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api);
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 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;
}
run() {
return __awaiter(this, void 0, void 0, function* () {
core.info('Running Remote Builder on Kubernetes');
const defaultSecretsArray = [
{
ParameterKey: 'GithubToken',
EnvironmentVariable: 'GITHUB_TOKEN',
ParameterValue: this.buildParameters.githubToken,
},
{
ParameterKey: 'UNITY_LICENSE',
EnvironmentVariable: 'UNITY_LICENSE',
ParameterValue: process.env.UNITY_LICENSE || '',
},
{
ParameterKey: 'ANDROID_KEYSTORE_BASE64',
EnvironmentVariable: 'ANDROID_KEYSTORE_BASE64',
ParameterValue: this.buildParameters.androidKeystoreBase64,
},
{
ParameterKey: 'ANDROID_KEYSTORE_PASS',
EnvironmentVariable: 'ANDROID_KEYSTORE_PASS',
ParameterValue: this.buildParameters.androidKeystorePass,
},
{
ParameterKey: 'ANDROID_KEYALIAS_PASS',
EnvironmentVariable: 'ANDROID_KEYALIAS_PASS',
ParameterValue: this.buildParameters.androidKeyaliasPass,
},
];
try {
// setup
yield this.createSecret(defaultSecretsArray);
yield this.createPersistentVolumeClaim();
// run
yield this.runCloneJob();
yield this.runBuildJob();
yield this.cleanup();
}
catch (error) {
core.error(JSON.stringify(error.response, undefined, 4));
throw error;
}
core.setOutput('volume', this.pvcName);
});
}
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) {
throw error;
}
});
}
getPVCPhase() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
return (_a = (yield this.kubeClient.readNamespacedPersistentVolumeClaimStatus(this.pvcName, this.namespace)).body.status) === null || _a === void 0 ? void 0 : _a.phase;
});
}
createPersistentVolumeClaim() {
return __awaiter(this, void 0, void 0, function* () {
if (this.buildParameters.kubeVolume) {
core.info(this.buildParameters.kubeVolume);
this.pvcName = this.buildParameters.kubeVolume;
return;
}
const pvc = new k8s.V1PersistentVolumeClaim();
pvc.apiVersion = 'v1';
pvc.kind = 'PersistentVolumeClaim';
pvc.metadata = {
name: this.pvcName,
};
pvc.spec = {
accessModes: ['ReadWriteOnce'],
volumeMode: 'Filesystem',
resources: {
requests: {
storage: this.buildParameters.kubeVolumeSize,
},
},
};
yield this.kubeClient.createNamespacedPersistentVolumeClaim(this.namespace, pvc);
core.info(`Persistent Volume created, ${yield this.getPVCPhase()}`);
});
}
getJobSpec(command, image) {
const job = new k8s.V1Job();
job.apiVersion = 'batch/v1';
job.kind = 'Job';
job.metadata = {
name: this.jobName,
labels: {
app: 'unity-builder',
},
};
job.spec = {
template: {
spec: {
volumes: [
{
name: 'data',
persistentVolumeClaim: {
claimName: this.pvcName,
},
},
{
name: 'credentials',
secret: {
secretName: this.secretName,
},
},
],
containers: [
{
name: 'main',
image,
command,
resources: {
requests: {
memory: this.buildParameters.remoteBuildMemory,
cpu: this.buildParameters.remoteBuildCpu,
},
},
env: [
{
name: 'GITHUB_SHA',
value: this.buildId,
},
{
name: 'GITHUB_WORKSPACE',
value: '/data/repo',
},
{
name: 'PROJECT_PATH',
value: this.buildParameters.projectPath,
},
{
name: 'BUILD_PATH',
value: this.buildParameters.buildPath,
},
{
name: 'BUILD_FILE',
value: this.buildParameters.buildFile,
},
{
name: 'BUILD_NAME',
value: this.buildParameters.buildName,
},
{
name: 'BUILD_METHOD',
value: this.buildParameters.buildMethod,
},
{
name: 'CUSTOM_PARAMETERS',
value: this.buildParameters.customParameters,
},
{
name: 'CHOWN_FILES_TO',
value: this.buildParameters.chownFilesTo,
},
{
name: 'BUILD_TARGET',
value: this.buildParameters.platform,
},
{
name: 'ANDROID_VERSION_CODE',
value: this.buildParameters.androidVersionCode.toString(),
},
{
name: 'ANDROID_KEYSTORE_NAME',
value: this.buildParameters.androidKeystoreName,
},
{
name: 'ANDROID_KEYALIAS_NAME',
value: this.buildParameters.androidKeyaliasName,
},
],
volumeMounts: [
{
name: 'data',
mountPath: '/data',
},
{
name: 'credentials',
mountPath: '/credentials',
readOnly: true,
},
],
lifecycle: {
preStop: {
exec: {
command: [
'bin/bash',
'-c',
`cd /data/builder/action/steps;
chmod +x /return_license.sh;
/return_license.sh;`,
],
},
},
},
},
],
restartPolicy: 'Never',
},
},
};
job.spec.backoffLimit = 1;
return job;
}
runJob(command, image) {
return __awaiter(this, void 0, void 0, function* () {
try {
this.setUniqueBuildId();
const jobSpec = this.getJobSpec(command, image);
core.info('Creating build job');
yield this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
core.info('Job created');
yield this.watchPersistentVolumeClaimUntilBoundToContainer();
core.info('PVC Bound');
this.setPodNameAndContainerName(yield this.getPod());
core.info('Watching pod and streaming logs');
yield this.watchUntilPodRunning();
yield this.streamLogs();
yield this.cleanup();
}
catch (error) {
yield this.cleanup();
throw error;
}
});
}
getPod() {
return __awaiter(this, void 0, void 0, function* () {
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; });
if (pod === undefined) {
throw new Error("pod with job-name label doesn't exist");
}
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',
'-c',
`apk update;
apk add git-lfs;
ls /credentials/
export GITHUB_TOKEN=$(cat /credentials/GITHUB_TOKEN);
cd /data;
git clone https://github.com/${process.env.GITHUB_REPOSITORY}.git repo;
git clone https://github.com/webbertakken/unity-builder.git builder;
cd repo;
git checkout $GITHUB_SHA;
ls
echo "end"`,
], 'alpine/git');
});
}
runBuildJob() {
return __awaiter(this, void 0, void 0, function* () {
yield this.runJob([
'bin/bash',
'-c',
`ls
for f in ./credentials/*; do export $(basename $f)="$(cat $f)"; done
ls /data
ls /data/builder
ls /data/builder/dist
cp -r /data/builder/dist/default-build-script /UnityBuilderAction
cp -r /data/builder/dist/entrypoint.sh /entrypoint.sh
cp -r /data/builder/dist/steps /steps
chmod -R +x /entrypoint.sh
chmod -R +x /steps
/entrypoint.sh
`,
], this.baseImage.toString());
});
}
watchPersistentVolumeClaimUntilBoundToContainer() {
return __awaiter(this, void 0, void 0, function* () {
yield async_wait_until_1.waitUntil(() => __awaiter(this, void 0, void 0, function* () { return (yield this.getPVCPhase()) !== 'Pending'; }));
});
}
getPodStatusPhase() {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
return (_b = (_a = (yield this.kubeClient.readNamespacedPod(this.podName, this.namespace))) === null || _a === void 0 ? void 0 : _a.body.status) === null || _b === void 0 ? void 0 : _b.phase;
});
}
watchUntilPodRunning() {
return __awaiter(this, void 0, void 0, function* () {
yield async_wait_until_1.waitUntil(() => __awaiter(this, void 0, void 0, function* () {
(yield this.getPodStatusPhase()) !== 'Pending';
}));
const phase = yield this.getPodStatusPhase();
if (phase === 'Running') {
core.info('Pod no longer pending');
}
else {
core.error('Pod failed to reach running phase');
}
});
}
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() {
return __awaiter(this, void 0, void 0, function* () {
core.info(`Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`);
const stream = new stream_1.Writable();
stream._write = (chunk, encoding, next) => {
core.info(chunk.toString());
next();
};
const logOptions = {
follow: true,
pretty: true,
previous: true,
};
try {
yield new Promise((resolve) => new client_node_1.Log(this.kubeConfig).log(this.namespace, this.podName, this.containerName, stream, resolve, logOptions));
}
catch (error) {
throw error;
}
core.info('end of log stream');
});
}
cleanup() {
return __awaiter(this, void 0, void 0, function* () {
core.info('cleaning up');
yield this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
yield this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
yield this.kubeClient.deleteNamespacedSecret(this.secretName, this.namespace);
});
}
static uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.trunc(Math.random() * 16);
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}
exports.default = Kubernetes;
/***/ }), /***/ }),
/***/ 85487: /***/ 85487:
@ -1655,6 +1211,503 @@ class AWSBuildRunner {
exports.default = AWSBuildRunner; exports.default = AWSBuildRunner;
/***/ }),
/***/ 81730:
/***/ (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 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);
class Kubernetes {
constructor(buildParameters, baseImage) {
this.buildId = '';
this.pvcName = '';
this.secretName = '';
this.jobName = '';
this.podName = '';
this.containerName = '';
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api);
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 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;
}
run() {
return __awaiter(this, void 0, void 0, function* () {
core.info('Running Remote Builder on Kubernetes');
const defaultSecretsArray = [
{
ParameterKey: 'GithubToken',
EnvironmentVariable: 'GITHUB_TOKEN',
ParameterValue: this.buildParameters.githubToken,
},
{
ParameterKey: 'UNITY_LICENSE',
EnvironmentVariable: 'UNITY_LICENSE',
ParameterValue: process.env.UNITY_LICENSE || '',
},
{
ParameterKey: 'ANDROID_KEYSTORE_BASE64',
EnvironmentVariable: 'ANDROID_KEYSTORE_BASE64',
ParameterValue: this.buildParameters.androidKeystoreBase64,
},
{
ParameterKey: 'ANDROID_KEYSTORE_PASS',
EnvironmentVariable: 'ANDROID_KEYSTORE_PASS',
ParameterValue: this.buildParameters.androidKeystorePass,
},
{
ParameterKey: 'ANDROID_KEYALIAS_PASS',
EnvironmentVariable: 'ANDROID_KEYALIAS_PASS',
ParameterValue: this.buildParameters.androidKeyaliasPass,
},
];
try {
// setup
yield this.createSecret(defaultSecretsArray);
yield kubernetes_storage_1.default.createPersistentVolumeClaim(this.buildParameters, this.pvcName, this.kubeClient, this.namespace);
// run
yield this.runCloneJob();
yield this.runBuildJob();
yield this.cleanup();
}
catch (error) {
core.error(JSON.stringify(error.response, undefined, 4));
throw error;
}
core.setOutput('volume', this.pvcName);
});
}
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) {
throw error;
}
});
}
getJobSpec(command, image) {
const job = new k8s.V1Job();
job.apiVersion = 'batch/v1';
job.kind = 'Job';
job.metadata = {
name: this.jobName,
labels: {
app: 'unity-builder',
},
};
job.spec = {
template: {
spec: {
volumes: [
{
name: 'data',
persistentVolumeClaim: {
claimName: this.pvcName,
},
},
{
name: 'credentials',
secret: {
secretName: this.secretName,
},
},
],
containers: [
{
name: 'main',
image,
command,
resources: {
requests: {
memory: this.buildParameters.remoteBuildMemory,
cpu: this.buildParameters.remoteBuildCpu,
},
},
env: [
{
name: 'GITHUB_SHA',
value: this.buildId,
},
{
name: 'GITHUB_WORKSPACE',
value: '/data/repo',
},
{
name: 'PROJECT_PATH',
value: this.buildParameters.projectPath,
},
{
name: 'BUILD_PATH',
value: this.buildParameters.buildPath,
},
{
name: 'BUILD_FILE',
value: this.buildParameters.buildFile,
},
{
name: 'BUILD_NAME',
value: this.buildParameters.buildName,
},
{
name: 'BUILD_METHOD',
value: this.buildParameters.buildMethod,
},
{
name: 'CUSTOM_PARAMETERS',
value: this.buildParameters.customParameters,
},
{
name: 'CHOWN_FILES_TO',
value: this.buildParameters.chownFilesTo,
},
{
name: 'BUILD_TARGET',
value: this.buildParameters.platform,
},
{
name: 'ANDROID_VERSION_CODE',
value: this.buildParameters.androidVersionCode.toString(),
},
{
name: 'ANDROID_KEYSTORE_NAME',
value: this.buildParameters.androidKeystoreName,
},
{
name: 'ANDROID_KEYALIAS_NAME',
value: this.buildParameters.androidKeyaliasName,
},
],
volumeMounts: [
{
name: 'data',
mountPath: '/data',
},
{
name: 'credentials',
mountPath: '/credentials',
readOnly: true,
},
],
lifecycle: {
preStop: {
exec: {
command: [
'bin/bash',
'-c',
`cd /data/builder/action/steps;
chmod +x /return_license.sh;
/return_license.sh;`,
],
},
},
},
},
],
restartPolicy: 'Never',
},
},
};
job.spec.backoffLimit = 1;
return job;
}
runJob(command, image) {
return __awaiter(this, void 0, void 0, function* () {
try {
this.setUniqueBuildId();
const jobSpec = this.getJobSpec(command, image);
core.info('Creating build job');
yield this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
core.info('Job created');
yield kubernetes_storage_1.default.watchPersistentVolumeClaimUntilBoundToContainer(this.kubeClient, this.pvcName, this.namespace);
core.info('PVC Bound');
this.setPodNameAndContainerName(yield this.getPod());
core.info('Watching pod and streaming logs');
yield this.watchUntilPodRunning();
yield this.streamLogs();
yield this.cleanup();
}
catch (error) {
yield this.cleanup();
throw error;
}
});
}
getPod() {
return __awaiter(this, void 0, void 0, function* () {
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; });
if (pod === undefined) {
throw new Error("pod with job-name label doesn't exist");
}
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',
'-c',
`apk update;
apk add git-lfs;
ls /credentials/
export GITHUB_TOKEN=$(cat /credentials/GITHUB_TOKEN);
cd /data;
git clone https://github.com/${process.env.GITHUB_REPOSITORY}.git repo;
git clone https://github.com/webbertakken/unity-builder.git builder;
cd repo;
git checkout $GITHUB_SHA;
ls
echo "end"`,
], 'alpine/git');
});
}
runBuildJob() {
return __awaiter(this, void 0, void 0, function* () {
yield this.runJob([
'bin/bash',
'-c',
`ls
for f in ./credentials/*; do export $(basename $f)="$(cat $f)"; done
ls /data
ls /data/builder
ls /data/builder/dist
cp -r /data/builder/dist/default-build-script /UnityBuilderAction
cp -r /data/builder/dist/entrypoint.sh /entrypoint.sh
cp -r /data/builder/dist/steps /steps
chmod -R +x /entrypoint.sh
chmod -R +x /steps
/entrypoint.sh
`,
], this.baseImage.toString());
});
}
getPodStatusPhase() {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
return (_b = (_a = (yield this.kubeClient.readNamespacedPod(this.podName, this.namespace))) === null || _a === void 0 ? void 0 : _a.body.status) === null || _b === void 0 ? void 0 : _b.phase;
});
}
watchUntilPodRunning() {
return __awaiter(this, void 0, void 0, function* () {
yield async_wait_until_1.waitUntil(() => __awaiter(this, void 0, void 0, function* () {
(yield this.getPodStatusPhase()) !== 'Pending';
}));
const phase = yield this.getPodStatusPhase();
if (phase === 'Running') {
core.info('Pod no longer pending');
}
else {
core.error('Pod failed to reach running phase');
}
});
}
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() {
return __awaiter(this, void 0, void 0, function* () {
core.info(`Streaming logs from pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace}`);
const stream = new stream_1.Writable();
stream._write = (chunk, encoding, next) => {
core.info(chunk.toString());
next();
};
const logOptions = {
follow: true,
pretty: true,
previous: true,
};
try {
yield new Promise((resolve) => new client_node_1.Log(this.kubeConfig).log(this.namespace, this.podName, this.containerName, stream, resolve, logOptions));
}
catch (error) {
throw error;
}
core.info('end of log stream');
});
}
cleanup() {
return __awaiter(this, void 0, void 0, function* () {
core.info('cleaning up');
yield this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
yield this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
yield this.kubeClient.deleteNamespacedSecret(this.secretName, this.namespace);
});
}
static uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.trunc(Math.random() * 16);
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}
exports.default = Kubernetes;
/***/ }),
/***/ 38941:
/***/ (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));
const k8s = __importStar(__webpack_require__(89679));
class KubernetesStorage {
static getPVCPhase(kubeClient, name, namespace) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
return (_a = (yield kubeClient.readNamespacedPersistentVolumeClaimStatus(name, namespace)).body.status) === null || _a === void 0 ? void 0 : _a.phase;
});
}
static watchPersistentVolumeClaimUntilBoundToContainer(kubeClient, name, namespace) {
return __awaiter(this, void 0, void 0, function* () {
yield async_wait_until_1.default(() => __awaiter(this, void 0, void 0, function* () { return (yield this.getPVCPhase(kubeClient, name, namespace)) !== 'Pending'; }));
});
}
static createPersistentVolumeClaim(buildParameters, pvcName, kubeClient, namespace) {
return __awaiter(this, void 0, void 0, function* () {
if (buildParameters.kubeVolume) {
core.info(buildParameters.kubeVolume);
pvcName = buildParameters.kubeVolume;
return;
}
const pvc = new k8s.V1PersistentVolumeClaim();
pvc.apiVersion = 'v1';
pvc.kind = 'PersistentVolumeClaim';
pvc.metadata = {
name: pvcName,
};
pvc.spec = {
accessModes: ['ReadWriteOnce'],
volumeMode: 'Filesystem',
resources: {
requests: {
storage: buildParameters.kubeVolumeSize,
},
},
};
yield kubeClient.createNamespacedPersistentVolumeClaim(namespace, pvc);
core.info(`Persistent Volume created, ${yield KubernetesStorage.getPVCPhase(kubeClient, pvcName, namespace)}`);
yield this.watchPersistentVolumeClaimUntilBoundToContainer(kubeClient, pvcName, pvcName);
core.info(JSON.stringify((yield kubeClient.readNamespacedPersistentVolumeClaimStatus(pvcName, namespace)).body, undefined, 4));
});
}
}
exports.default = KubernetesStorage;
/***/ }), /***/ }),
/***/ 92560: /***/ 92560:

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ import Platform from './platform';
import Project from './project'; import Project from './project';
import Unity from './unity'; import Unity from './unity';
import Versioning from './versioning'; import Versioning from './versioning';
import Kubernetes from './kubernetes'; import Kubernetes from './remote-builder/kubernetes-build-platform';
import RemoteBuilder from './remote-builder/remote-builder'; import RemoteBuilder from './remote-builder/remote-builder';
export { export {

View File

@ -1,11 +1,12 @@
import * as k8s from '@kubernetes/client-node'; import * as k8s from '@kubernetes/client-node';
import { BuildParameters } from '.'; 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'; import { RemoteBuilderProviderInterface } from './remote-builder-provider-interface';
import RemoteBuilderSecret from './remote-builder/remote-builder-secret'; import RemoteBuilderSecret from './remote-builder-secret';
import { waitUntil } from 'async-wait-until'; import { waitUntil } from 'async-wait-until';
import KubernetesStorage from './kubernetes-storage';
const base64 = require('base-64'); const base64 = require('base-64');
@ -85,7 +86,13 @@ class Kubernetes implements RemoteBuilderProviderInterface {
try { try {
// setup // setup
await this.createSecret(defaultSecretsArray); await this.createSecret(defaultSecretsArray);
await this.createPersistentVolumeClaim(); await KubernetesStorage.createPersistentVolumeClaim(
this.buildParameters,
this.pvcName,
this.kubeClient,
this.namespace,
);
// run // run
await this.runCloneJob(); await this.runCloneJob();
await this.runBuildJob(); await this.runBuildJob();
@ -119,36 +126,6 @@ class Kubernetes implements RemoteBuilderProviderInterface {
} }
} }
async getPVCPhase() {
return (await this.kubeClient.readNamespacedPersistentVolumeClaimStatus(this.pvcName, this.namespace)).body.status
?.phase;
}
async createPersistentVolumeClaim() {
if (this.buildParameters.kubeVolume) {
core.info(this.buildParameters.kubeVolume);
this.pvcName = this.buildParameters.kubeVolume;
return;
}
const pvc = new k8s.V1PersistentVolumeClaim();
pvc.apiVersion = 'v1';
pvc.kind = 'PersistentVolumeClaim';
pvc.metadata = {
name: this.pvcName,
};
pvc.spec = {
accessModes: ['ReadWriteOnce'],
volumeMode: 'Filesystem',
resources: {
requests: {
storage: this.buildParameters.kubeVolumeSize,
},
},
};
await this.kubeClient.createNamespacedPersistentVolumeClaim(this.namespace, pvc);
core.info(`Persistent Volume created, ${await this.getPVCPhase()}`);
}
getJobSpec(command: string[], image: string) { getJobSpec(command: string[], image: string) {
const job = new k8s.V1Job(); const job = new k8s.V1Job();
job.apiVersion = 'batch/v1'; job.apiVersion = 'batch/v1';
@ -282,7 +259,11 @@ class Kubernetes implements RemoteBuilderProviderInterface {
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');
await this.watchPersistentVolumeClaimUntilBoundToContainer(); await KubernetesStorage.watchPersistentVolumeClaimUntilBoundToContainer(
this.kubeClient,
this.pvcName,
this.namespace,
);
core.info('PVC Bound'); core.info('PVC Bound');
this.setPodNameAndContainerName(await this.getPod()); this.setPodNameAndContainerName(await this.getPod());
core.info('Watching pod and streaming logs'); core.info('Watching pod and streaming logs');
@ -352,10 +333,6 @@ class Kubernetes implements RemoteBuilderProviderInterface {
); );
} }
async watchPersistentVolumeClaimUntilBoundToContainer() {
await waitUntil(async () => (await this.getPVCPhase()) !== 'Pending');
}
async getPodStatusPhase() { async getPodStatusPhase() {
return (await this.kubeClient.readNamespacedPod(this.podName, this.namespace))?.body.status?.phase; return (await this.kubeClient.readNamespacedPod(this.podName, this.namespace))?.body.status?.phase;
} }

View File

@ -0,0 +1,47 @@
import waitUntil from 'async-wait-until';
import * as core from '@actions/core';
import * as k8s from '@kubernetes/client-node';
class KubernetesStorage {
public static async getPVCPhase(kubeClient, name, namespace) {
return (await kubeClient.readNamespacedPersistentVolumeClaimStatus(name, namespace)).body.status?.phase;
}
public static async watchPersistentVolumeClaimUntilBoundToContainer(kubeClient, name, namespace) {
await waitUntil(async () => (await this.getPVCPhase(kubeClient, name, namespace)) !== 'Pending');
}
public static async createPersistentVolumeClaim(buildParameters, pvcName, kubeClient, namespace) {
if (buildParameters.kubeVolume) {
core.info(buildParameters.kubeVolume);
pvcName = buildParameters.kubeVolume;
return;
}
const pvc = new k8s.V1PersistentVolumeClaim();
pvc.apiVersion = 'v1';
pvc.kind = 'PersistentVolumeClaim';
pvc.metadata = {
name: pvcName,
};
pvc.spec = {
accessModes: ['ReadWriteOnce'],
volumeMode: 'Filesystem',
resources: {
requests: {
storage: buildParameters.kubeVolumeSize,
},
},
};
await kubeClient.createNamespacedPersistentVolumeClaim(namespace, pvc);
core.info(`Persistent Volume created, ${await KubernetesStorage.getPVCPhase(kubeClient, pvcName, namespace)}`);
await this.watchPersistentVolumeClaimUntilBoundToContainer(kubeClient, pvcName, pvcName);
core.info(
JSON.stringify(
(await kubeClient.readNamespacedPersistentVolumeClaimStatus(pvcName, namespace)).body,
undefined,
4,
),
);
}
}
export default KubernetesStorage;