Remove stackName from generic buildPlatform interface

pull/273/head
Frostebite 2021-06-19 21:35:22 +01:00
parent 2b559c587f
commit b33e19aa34
6 changed files with 58 additions and 255 deletions

151
dist/index.js vendored
View File

@ -848,28 +848,31 @@ const core = __importStar(__webpack_require__(42186));
const remote_builder_constants_1 = __importDefault(__webpack_require__(92560)); const remote_builder_constants_1 = __importDefault(__webpack_require__(92560));
const aws_build_runner_1 = __importDefault(__webpack_require__(11201)); const aws_build_runner_1 = __importDefault(__webpack_require__(11201));
class AWSBuildEnvironment { class AWSBuildEnvironment {
runBuildTask(buildId, stackName, image, commands, mountdir, workingdir, environment, secrets) { constructor(buildParameters) {
this.stackName = buildParameters.awsStackName;
}
runBuildTask(buildId, image, commands, mountdir, workingdir, environment, secrets) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const ECS = new SDK.ECS(); const ECS = new SDK.ECS();
const CF = new SDK.CloudFormation(); const CF = new SDK.CloudFormation();
const entrypoint = ['/bin/sh']; const entrypoint = ['/bin/sh'];
const taskDef = yield AWSBuildEnvironment.setupCloudFormations(CF, buildId, stackName, image, entrypoint, commands, mountdir, workingdir, secrets); const taskDef = yield this.setupCloudFormations(CF, buildId, image, entrypoint, commands, mountdir, workingdir, secrets);
try { try {
yield aws_build_runner_1.default.runTask(taskDef, ECS, CF, environment, buildId); yield aws_build_runner_1.default.runTask(taskDef, ECS, CF, environment, buildId);
} }
finally { finally {
yield AWSBuildEnvironment.cleanupResources(CF, taskDef); yield this.cleanupResources(CF, taskDef);
} }
}); });
} }
static getParameterTemplate(p1) { getParameterTemplate(p1) {
return ` return `
${p1}: ${p1}:
Type: String Type: String
Default: '' Default: ''
`; `;
} }
static getSecretTemplate(p1) { getSecretTemplate(p1) {
return ` return `
${p1}Secret: ${p1}Secret:
Type: AWS::SecretsManager::Secret Type: AWS::SecretsManager::Secret
@ -878,24 +881,24 @@ class AWSBuildEnvironment {
SecretString: !Ref ${p1} SecretString: !Ref ${p1}
`; `;
} }
static getSecretDefinitionTemplate(p1, p2) { getSecretDefinitionTemplate(p1, p2) {
return ` return `
- Name: '${p1}' - Name: '${p1}'
ValueFrom: !Ref ${p2}Secret ValueFrom: !Ref ${p2}Secret
`; `;
} }
static insertAtTemplate(template, insertionKey, insertion) { insertAtTemplate(template, insertionKey, insertion) {
const index = template.search(insertionKey) + insertionKey.length + '\n'.length; const index = template.search(insertionKey) + insertionKey.length + '\n'.length;
template = [template.slice(0, index), insertion, template.slice(index)].join(''); template = [template.slice(0, index), insertion, template.slice(index)].join('');
return template; return template;
} }
static setupCloudFormations(CF, buildUid, stackName, image, entrypoint, commands, mountdir, workingdir, secrets) { setupCloudFormations(CF, buildUid, image, entrypoint, commands, mountdir, workingdir, secrets) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const logid = nanoid_1.customAlphabet(remote_builder_constants_1.default.alphabet, 9)(); const logid = nanoid_1.customAlphabet(remote_builder_constants_1.default.alphabet, 9)();
commands[1] += ` commands[1] += `
echo "${logid}" echo "${logid}"
`; `;
const taskDefStackName = `${stackName}-${buildUid}`; const taskDefStackName = `${this.stackName}-${buildUid}`;
let taskDefCloudFormation = this.readTaskCloudFormationTemplate(); let taskDefCloudFormation = this.readTaskCloudFormationTemplate();
const cleanupTaskDefStackName = `${taskDefStackName}-cleanup`; const cleanupTaskDefStackName = `${taskDefStackName}-cleanup`;
const cleanupCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/cloudformation-stack-ttl.yml`, 'utf8'); const cleanupCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/cloudformation-stack-ttl.yml`, 'utf8');
@ -971,13 +974,13 @@ class AWSBuildEnvironment {
yield CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise(); yield CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise();
} }
catch (error) { catch (error) {
yield AWSBuildEnvironment.handleStackCreationFailure(error, CF, taskDefStackName, taskDefCloudFormation, secrets); yield this.handleStackCreationFailure(error, CF, taskDefStackName, taskDefCloudFormation, secrets);
throw error; throw error;
} }
const taskDefResources = (yield CF.describeStackResources({ const taskDefResources = (yield CF.describeStackResources({
StackName: taskDefStackName, StackName: taskDefStackName,
}).promise()).StackResources; }).promise()).StackResources;
const baseResources = (yield CF.describeStackResources({ StackName: stackName }).promise()).StackResources; const baseResources = (yield CF.describeStackResources({ StackName: this.stackName }).promise()).StackResources;
// in the future we should offer a parameter to choose if you want the guarnteed shutdown. // in the future we should offer a parameter to choose if you want the guarnteed shutdown.
core.info('Worker cluster created successfully (skipping wait for cleanup cluster to be ready)'); core.info('Worker cluster created successfully (skipping wait for cleanup cluster to be ready)');
return { return {
@ -991,7 +994,7 @@ class AWSBuildEnvironment {
}; };
}); });
} }
static handleStackCreationFailure(error, CF, taskDefStackName, taskDefCloudFormation, secrets) { handleStackCreationFailure(error, CF, taskDefStackName, taskDefCloudFormation, secrets) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info(JSON.stringify(secrets, undefined, 4)); core.info(JSON.stringify(secrets, undefined, 4));
core.info(taskDefCloudFormation); core.info(taskDefCloudFormation);
@ -1002,10 +1005,10 @@ class AWSBuildEnvironment {
core.error(error); core.error(error);
}); });
} }
static readTaskCloudFormationTemplate() { readTaskCloudFormationTemplate() {
return fs.readFileSync(`${__dirname}/cloud-formations/task-def-formation.yml`, 'utf8'); return fs.readFileSync(`${__dirname}/cloud-formations/task-def-formation.yml`, 'utf8');
} }
static cleanupResources(CF, taskDef) { cleanupResources(CF, taskDef) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('Cleanup starting'); core.info('Cleanup starting');
yield CF.deleteStack({ yield CF.deleteStack({
@ -1249,9 +1252,8 @@ const async_wait_until_1 = __webpack_require__(41299);
const kubernetes_storage_1 = __importDefault(__webpack_require__(38941)); const kubernetes_storage_1 = __importDefault(__webpack_require__(38941));
const base64 = __webpack_require__(85848); const base64 = __webpack_require__(85848);
class Kubernetes { class Kubernetes {
constructor(buildParameters, baseImage) { constructor(buildParameters) {
this.buildId = ''; this.buildId = '';
this.buildCorrelationId = '';
this.pvcName = ''; this.pvcName = '';
this.secretName = ''; this.secretName = '';
this.jobName = ''; this.jobName = '';
@ -1265,45 +1267,15 @@ class Kubernetes {
this.kubeConfig = kc; this.kubeConfig = kc;
this.kubeClient = k8sApi; this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi; this.kubeClientBatch = k8sBatchApi;
this.buildCorrelationId = Kubernetes.uuidv4();
this.namespace = 'default'; this.namespace = 'default';
this.buildParameters = buildParameters; this.buildParameters = buildParameters;
this.baseImage = baseImage;
} }
runBuildTask(buildId, stackName, image, commands, mountdir, workingdir, environment, secrets) { runBuildTask(buildId, image, commands, mountdir, workingdir, environment, secrets) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
this.setUniqueBuildId(); this.setUniqueBuildId(buildId);
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,
},
];
defaultSecretsArray.push(...secrets);
// setup // setup
yield this.createSecret(defaultSecretsArray); yield this.createSecret(secrets);
yield kubernetes_storage_1.default.createPersistentVolumeClaim(this.buildParameters, this.pvcName, this.kubeClient, this.namespace); yield kubernetes_storage_1.default.createPersistentVolumeClaim(this.buildParameters, this.pvcName, this.kubeClient, this.namespace);
//run //run
const jobSpec = this.getJobSpec(commands, image, mountdir, workingdir, environment); const jobSpec = this.getJobSpec(commands, image, mountdir, workingdir, environment);
@ -1321,14 +1293,13 @@ class Kubernetes {
} }
catch (error) { catch (error) {
core.info('Running job failed'); core.info('Running job failed');
core.error(JSON.stringify(error, undefined, 4));
yield this.cleanup(); yield this.cleanup();
core.error(JSON.stringify(error.response, undefined, 4));
throw error; throw error;
} }
}); });
} }
setUniqueBuildId() { setUniqueBuildId(buildId) {
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}`;
@ -1486,61 +1457,6 @@ class Kubernetes {
}; };
return job; return job;
} }
runJobInKubernetesPod(command, image) {
return __awaiter(this, void 0, void 0, function* () {
try {
this.setUniqueBuildId();
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,
},
];
// setup
yield this.createSecret(defaultSecretsArray);
yield kubernetes_storage_1.default.createPersistentVolumeClaim(this.buildParameters, this.pvcName, this.kubeClient, this.namespace);
//run
const jobSpec = this.getJobSpec(command, image, 'data', 'data', []);
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());
core.info('Watching pod until running');
yield this.watchUntilPodRunning();
core.info('Pod running, streaming logs');
yield this.streamLogs();
yield this.cleanup();
}
catch (error) {
core.info('Running job failed');
yield this.cleanup();
throw error;
}
});
}
findPod() { findPod() {
return __awaiter(this, void 0, void 0, function* () { 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; }); 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; });
@ -1631,13 +1547,6 @@ class Kubernetes {
} }
}); });
} }
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; exports.default = Kubernetes;
@ -1822,12 +1731,12 @@ class RemoteBuilder {
switch (buildParameters.remoteBuildCluster) { switch (buildParameters.remoteBuildCluster) {
case 'aws': case 'aws':
core.info('Building with AWS'); core.info('Building with AWS');
this.RemoteBuilderProviderPlatform = new aws_build_platform_1.default(); this.RemoteBuilderProviderPlatform = new aws_build_platform_1.default(buildParameters);
break; break;
default: default:
case 'k8s': case 'k8s':
core.info('Building with Kubernetes'); core.info('Building with Kubernetes');
this.RemoteBuilderProviderPlatform = new kubernetes_build_platform_1.default(buildParameters, baseImage); this.RemoteBuilderProviderPlatform = new kubernetes_build_platform_1.default(buildParameters);
break; break;
} }
this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false; this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false;
@ -1862,7 +1771,7 @@ class RemoteBuilder {
static SetupStep(buildUid, buildParameters, branchName, defaultSecretsArray) { static SetupStep(buildUid, buildParameters, branchName, defaultSecretsArray) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('Starting step 1/4 clone and restore cache)'); core.info('Starting step 1/4 clone and restore cache)');
yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, buildParameters.awsStackName, 'alpine/git', [ yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, 'alpine/git', [
'-c', '-c',
`apk update; `apk update;
apk add unzip; apk add unzip;
@ -1974,7 +1883,7 @@ class RemoteBuilder {
ParameterValue: buildParameters.androidKeyaliasPass, ParameterValue: buildParameters.androidKeyaliasPass,
}); });
core.info('Starting part 2/4 (build unity project)'); core.info('Starting part 2/4 (build unity project)');
yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, buildParameters.awsStackName, baseImage.toString(), [ yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, baseImage.toString(), [
'-c', '-c',
` `
cp -r /${buildVolumeFolder}/${buildUid}/builder/dist/default-build-script/ /UnityBuilderAction; cp -r /${buildVolumeFolder}/${buildUid}/builder/dist/default-build-script/ /UnityBuilderAction;
@ -2044,7 +1953,7 @@ class RemoteBuilder {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('Starting step 3/4 build compression'); core.info('Starting step 3/4 build compression');
// Cleanup // Cleanup
yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, buildParameters.awsStackName, 'alpine', [ yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, 'alpine', [
'-c', '-c',
` `
apk update apk update
@ -2071,7 +1980,7 @@ class RemoteBuilder {
static UploadArtifacts(buildUid, buildParameters, branchName, defaultSecretsArray) { static UploadArtifacts(buildUid, buildParameters, branchName, defaultSecretsArray) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('Starting step 4/4 upload build to s3'); core.info('Starting step 4/4 upload build to s3');
yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, buildParameters.awsStackName, 'amazon/aws-cli', [ yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, 'amazon/aws-cli', [
'-c', '-c',
` `
aws s3 cp ${buildUid}/build-${buildUid}.zip s3://game-ci-storage/ aws s3 cp ${buildUid}/build-${buildUid}.zip s3://game-ci-storage/
@ -2106,7 +2015,7 @@ class RemoteBuilder {
static DeployToSteam(buildUid, buildParameters, defaultSecretsArray) { static DeployToSteam(buildUid, buildParameters, defaultSecretsArray) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info('Starting steam deployment'); core.info('Starting steam deployment');
yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, buildParameters.awsStackName, 'cm2network/steamcmd:root', [ yield this.RemoteBuilderProviderPlatform.runBuildTask(buildUid, 'cm2network/steamcmd:root', [
'-c', '-c',
` `
ls ls

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -8,11 +8,17 @@ import RemoteBuilderTaskDef from './remote-builder-task-def';
import RemoteBuilderConstants from './remote-builder-constants'; import RemoteBuilderConstants from './remote-builder-constants';
import AWSBuildRunner from './aws-build-runner'; import AWSBuildRunner from './aws-build-runner';
import { RemoteBuilderProviderInterface } from './remote-builder-provider-interface'; import { RemoteBuilderProviderInterface } from './remote-builder-provider-interface';
import BuildParameters from '../build-parameters';
class AWSBuildEnvironment implements RemoteBuilderProviderInterface { class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
private stackName: string;
constructor(buildParameters: BuildParameters) {
this.stackName = buildParameters.awsStackName;
}
async runBuildTask( async runBuildTask(
buildId: string, buildId: string,
stackName: string,
image: string, image: string,
commands: string[], commands: string[],
mountdir: string, mountdir: string,
@ -24,10 +30,9 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
const CF = new SDK.CloudFormation(); const CF = new SDK.CloudFormation();
const entrypoint = ['/bin/sh']; const entrypoint = ['/bin/sh'];
const taskDef = await AWSBuildEnvironment.setupCloudFormations( const taskDef = await this.setupCloudFormations(
CF, CF,
buildId, buildId,
stackName,
image, image,
entrypoint, entrypoint,
commands, commands,
@ -38,11 +43,11 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
try { try {
await AWSBuildRunner.runTask(taskDef, ECS, CF, environment, buildId); await AWSBuildRunner.runTask(taskDef, ECS, CF, environment, buildId);
} finally { } finally {
await AWSBuildEnvironment.cleanupResources(CF, taskDef); await this.cleanupResources(CF, taskDef);
} }
} }
static getParameterTemplate(p1) { getParameterTemplate(p1) {
return ` return `
${p1}: ${p1}:
Type: String Type: String
@ -50,7 +55,7 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
`; `;
} }
static getSecretTemplate(p1) { getSecretTemplate(p1) {
return ` return `
${p1}Secret: ${p1}Secret:
Type: AWS::SecretsManager::Secret Type: AWS::SecretsManager::Secret
@ -60,23 +65,22 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
`; `;
} }
static getSecretDefinitionTemplate(p1, p2) { getSecretDefinitionTemplate(p1, p2) {
return ` return `
- Name: '${p1}' - Name: '${p1}'
ValueFrom: !Ref ${p2}Secret ValueFrom: !Ref ${p2}Secret
`; `;
} }
static insertAtTemplate(template, insertionKey, insertion) { insertAtTemplate(template, insertionKey, insertion) {
const index = template.search(insertionKey) + insertionKey.length + '\n'.length; const index = template.search(insertionKey) + insertionKey.length + '\n'.length;
template = [template.slice(0, index), insertion, template.slice(index)].join(''); template = [template.slice(0, index), insertion, template.slice(index)].join('');
return template; return template;
} }
static async setupCloudFormations( async setupCloudFormations(
CF: SDK.CloudFormation, CF: SDK.CloudFormation,
buildUid: string, buildUid: string,
stackName: string,
image: string, image: string,
entrypoint: string[], entrypoint: string[],
commands: string[], commands: string[],
@ -88,7 +92,7 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
commands[1] += ` commands[1] += `
echo "${logid}" echo "${logid}"
`; `;
const taskDefStackName = `${stackName}-${buildUid}`; const taskDefStackName = `${this.stackName}-${buildUid}`;
let taskDefCloudFormation = this.readTaskCloudFormationTemplate(); let taskDefCloudFormation = this.readTaskCloudFormationTemplate();
const cleanupTaskDefStackName = `${taskDefStackName}-cleanup`; const cleanupTaskDefStackName = `${taskDefStackName}-cleanup`;
const cleanupCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/cloudformation-stack-ttl.yml`, 'utf8'); const cleanupCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/cloudformation-stack-ttl.yml`, 'utf8');
@ -178,7 +182,7 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise(); await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise();
} catch (error) { } catch (error) {
await AWSBuildEnvironment.handleStackCreationFailure(error, CF, taskDefStackName, taskDefCloudFormation, secrets); await this.handleStackCreationFailure(error, CF, taskDefStackName, taskDefCloudFormation, secrets);
throw error; throw error;
} }
@ -189,7 +193,7 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
}).promise() }).promise()
).StackResources; ).StackResources;
const baseResources = (await CF.describeStackResources({ StackName: stackName }).promise()).StackResources; const baseResources = (await CF.describeStackResources({ StackName: this.stackName }).promise()).StackResources;
// in the future we should offer a parameter to choose if you want the guarnteed shutdown. // in the future we should offer a parameter to choose if you want the guarnteed shutdown.
core.info('Worker cluster created successfully (skipping wait for cleanup cluster to be ready)'); core.info('Worker cluster created successfully (skipping wait for cleanup cluster to be ready)');
@ -205,7 +209,7 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
}; };
} }
private static async handleStackCreationFailure( async handleStackCreationFailure(
error: any, error: any,
CF: SDK.CloudFormation, CF: SDK.CloudFormation,
taskDefStackName: string, taskDefStackName: string,
@ -221,11 +225,11 @@ class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
core.error(error); core.error(error);
} }
static readTaskCloudFormationTemplate(): string { readTaskCloudFormationTemplate(): string {
return fs.readFileSync(`${__dirname}/cloud-formations/task-def-formation.yml`, 'utf8'); return fs.readFileSync(`${__dirname}/cloud-formations/task-def-formation.yml`, 'utf8');
} }
static async cleanupResources(CF: SDK.CloudFormation, taskDef: RemoteBuilderTaskDef) { async cleanupResources(CF: SDK.CloudFormation, taskDef: RemoteBuilderTaskDef) {
core.info('Cleanup starting'); core.info('Cleanup starting');
await CF.deleteStack({ await CF.deleteStack({
StackName: taskDef.taskDefStackName, StackName: taskDef.taskDefStackName,

View File

@ -15,9 +15,7 @@ class Kubernetes implements RemoteBuilderProviderInterface {
private kubeClient: k8s.CoreV1Api; private kubeClient: k8s.CoreV1Api;
private kubeClientBatch: k8s.BatchV1Api; private kubeClientBatch: k8s.BatchV1Api;
private buildId: string = ''; private buildId: string = '';
private buildCorrelationId: string = '';
private buildParameters: BuildParameters; private buildParameters: BuildParameters;
private baseImage: any;
private pvcName: string = ''; private pvcName: string = '';
private secretName: string = ''; private secretName: string = '';
private jobName: string = ''; private jobName: string = '';
@ -25,7 +23,7 @@ class Kubernetes implements RemoteBuilderProviderInterface {
private podName: string = ''; private podName: string = '';
private containerName: string = ''; private containerName: string = '';
constructor(buildParameters: BuildParameters, baseImage) { constructor(buildParameters: BuildParameters) {
const kc = new k8s.KubeConfig(); const kc = new k8s.KubeConfig();
kc.loadFromDefault(); kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api); const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
@ -36,15 +34,11 @@ class Kubernetes implements RemoteBuilderProviderInterface {
this.kubeClient = k8sApi; this.kubeClient = k8sApi;
this.kubeClientBatch = k8sBatchApi; this.kubeClientBatch = k8sBatchApi;
this.buildCorrelationId = Kubernetes.uuidv4();
this.namespace = 'default'; this.namespace = 'default';
this.buildParameters = buildParameters; this.buildParameters = buildParameters;
this.baseImage = baseImage;
} }
async runBuildTask( async runBuildTask(
buildId: string, buildId: string,
stackName: string,
image: string, image: string,
commands: string[], commands: string[],
mountdir: string, mountdir: string,
@ -53,37 +47,9 @@ class Kubernetes implements RemoteBuilderProviderInterface {
secrets: RemoteBuilderSecret[], secrets: RemoteBuilderSecret[],
): Promise<void> { ): Promise<void> {
try { try {
this.setUniqueBuildId(); this.setUniqueBuildId(buildId);
const defaultSecretsArray: RemoteBuilderSecret[] = [
{
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,
},
];
defaultSecretsArray.push(...secrets);
// setup // setup
await this.createSecret(defaultSecretsArray); await this.createSecret(secrets);
await KubernetesStorage.createPersistentVolumeClaim( await KubernetesStorage.createPersistentVolumeClaim(
this.buildParameters, this.buildParameters,
this.pvcName, this.pvcName,
@ -106,14 +72,13 @@ class Kubernetes implements RemoteBuilderProviderInterface {
await this.cleanup(); await this.cleanup();
} catch (error) { } catch (error) {
core.info('Running job failed'); core.info('Running job failed');
core.error(JSON.stringify(error, undefined, 4));
await this.cleanup(); await this.cleanup();
core.error(JSON.stringify(error.response, undefined, 4));
throw error; throw error;
} }
} }
setUniqueBuildId() { setUniqueBuildId(buildId: string) {
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}`;
@ -280,66 +245,6 @@ class Kubernetes implements RemoteBuilderProviderInterface {
return job; return job;
} }
async runJobInKubernetesPod(command: string[], image: string) {
try {
this.setUniqueBuildId();
const defaultSecretsArray: RemoteBuilderSecret[] = [
{
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,
},
];
// setup
await this.createSecret(defaultSecretsArray);
await KubernetesStorage.createPersistentVolumeClaim(
this.buildParameters,
this.pvcName,
this.kubeClient,
this.namespace,
);
//run
const jobSpec = this.getJobSpec(command, image, 'data', 'data', []);
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());
core.info('Watching pod until running');
await this.watchUntilPodRunning();
core.info('Pod running, streaming logs');
await this.streamLogs();
await this.cleanup();
} catch (error) {
core.info('Running job failed');
await this.cleanup();
throw error;
}
}
async findPod() { async findPod() {
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,
@ -434,13 +339,5 @@ class Kubernetes implements RemoteBuilderProviderInterface {
core.info('Abandoning cleanup, build error:'); core.info('Abandoning cleanup, build error:');
} }
} }
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);
});
}
} }
export default Kubernetes; export default Kubernetes;

View File

@ -6,8 +6,6 @@ export interface RemoteBuilderProviderInterface {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
buildId: string, buildId: string,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
stackName: string,
// eslint-disable-next-line no-unused-vars
image: string, image: string,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
commands: string[], commands: string[],

View File

@ -17,12 +17,12 @@ class RemoteBuilder {
switch (buildParameters.remoteBuildCluster) { switch (buildParameters.remoteBuildCluster) {
case 'aws': case 'aws':
core.info('Building with AWS'); core.info('Building with AWS');
this.RemoteBuilderProviderPlatform = new AWSBuildPlatform(); this.RemoteBuilderProviderPlatform = new AWSBuildPlatform(buildParameters);
break; break;
default: default:
case 'k8s': case 'k8s':
core.info('Building with Kubernetes'); core.info('Building with Kubernetes');
this.RemoteBuilderProviderPlatform = new Kubernetes(buildParameters, baseImage); this.RemoteBuilderProviderPlatform = new Kubernetes(buildParameters);
break; break;
} }
this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false; this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false;
@ -69,7 +69,6 @@ class RemoteBuilder {
core.info('Starting step 1/4 clone and restore cache)'); core.info('Starting step 1/4 clone and restore cache)');
await this.RemoteBuilderProviderPlatform.runBuildTask( await this.RemoteBuilderProviderPlatform.runBuildTask(
buildUid, buildUid,
buildParameters.awsStackName,
'alpine/git', 'alpine/git',
[ [
'-c', '-c',
@ -204,7 +203,6 @@ class RemoteBuilder {
core.info('Starting part 2/4 (build unity project)'); core.info('Starting part 2/4 (build unity project)');
await this.RemoteBuilderProviderPlatform.runBuildTask( await this.RemoteBuilderProviderPlatform.runBuildTask(
buildUid, buildUid,
buildParameters.awsStackName,
baseImage.toString(), baseImage.toString(),
[ [
'-c', '-c',
@ -287,7 +285,6 @@ class RemoteBuilder {
// Cleanup // Cleanup
await this.RemoteBuilderProviderPlatform.runBuildTask( await this.RemoteBuilderProviderPlatform.runBuildTask(
buildUid, buildUid,
buildParameters.awsStackName,
'alpine', 'alpine',
[ [
'-c', '-c',
@ -327,7 +324,6 @@ class RemoteBuilder {
core.info('Starting step 4/4 upload build to s3'); core.info('Starting step 4/4 upload build to s3');
await this.RemoteBuilderProviderPlatform.runBuildTask( await this.RemoteBuilderProviderPlatform.runBuildTask(
buildUid, buildUid,
buildParameters.awsStackName,
'amazon/aws-cli', 'amazon/aws-cli',
[ [
'-c', '-c',
@ -374,7 +370,6 @@ class RemoteBuilder {
core.info('Starting steam deployment'); core.info('Starting steam deployment');
await this.RemoteBuilderProviderPlatform.runBuildTask( await this.RemoteBuilderProviderPlatform.runBuildTask(
buildUid, buildUid,
buildParameters.awsStackName,
'cm2network/steamcmd:root', 'cm2network/steamcmd:root',
[ [
'-c', '-c',