Refactoring Remote Builder to route to aws or k8s

pull/273/head
Frostebite 2021-06-18 21:36:45 +01:00
parent 49d581a167
commit 0149cf79f2
7 changed files with 196 additions and 29 deletions

80
dist/index.js vendored
View File

@ -854,17 +854,20 @@ 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 {
static runBuild(buildId, stackName, image, commands, mountdir, workingdir, environment, secrets) { run() {
throw new Error('Method not implemented.');
}
runBuild(buildId, stackName, 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 this.setupCloudFormations(CF, buildId, stackName, image, entrypoint, commands, mountdir, workingdir, secrets); const taskDef = yield AWSBuildEnvironment.setupCloudFormations(CF, buildId, stackName, 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 this.cleanupResources(CF, taskDef); yield AWSBuildEnvironment.cleanupResources(CF, taskDef);
} }
}); });
} }
@ -1278,6 +1281,50 @@ class Kubernetes {
this.baseImage = baseImage; this.baseImage = baseImage;
this.setUniqueBuildId(); this.setUniqueBuildId();
} }
runBuild(buildId, stackName, image, commands, mountdir, workingdir, environment, secrets) {
return __awaiter(this, void 0, void 0, function* () {
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);
try {
// setup
yield this.createSecret(defaultSecretsArray);
yield kubernetes_storage_1.default.createPersistentVolumeClaim(this.buildParameters, this.pvcName, this.kubeClient, this.namespace);
// run
yield this.runJob(commands, image);
yield this.cleanup();
}
catch (error) {
core.error(JSON.stringify(error.response, undefined, 4));
throw error;
}
});
}
setUniqueBuildId() { setUniqueBuildId() {
const buildId = Kubernetes.uuidv4(); const buildId = Kubernetes.uuidv4();
const pvcName = `unity-builder-pvc-${buildId}`; const pvcName = `unity-builder-pvc-${buildId}`;
@ -1676,6 +1723,11 @@ class KubernetesStorage {
yield async_wait_until_1.default(() => __awaiter(this, void 0, void 0, function* () { return (yield this.getPVCPhase(kubeClient, name, namespace)) !== 'Pending'; }), { yield async_wait_until_1.default(() => __awaiter(this, void 0, void 0, function* () { return (yield this.getPVCPhase(kubeClient, name, namespace)) !== 'Pending'; }), {
timeout: 500000, timeout: 500000,
}); });
core.info(JSON.stringify((yield kubeClient.readNamespacedPersistentVolumeClaimStatus(name, namespace)).body, undefined, 4));
const pvc = (yield kubeClient.readNamespacedPersistentVolumeClaim(name, namespace)).body;
core.info(JSON.stringify(pvc, undefined, 4));
core.info(JSON.stringify((yield kubeClient.readNamespacedPersistentVolumeClaim(name, namespace)).body, undefined, 4));
core.info(JSON.stringify((yield kubeClient.replaceNamespacedPersistentVolumeClaimStatus(name, namespace, pvc)).body, undefined, 4));
}); });
} }
static createPersistentVolumeClaim(buildParameters, pvcName, kubeClient, namespace) { static createPersistentVolumeClaim(buildParameters, pvcName, kubeClient, namespace) {
@ -1702,8 +1754,6 @@ class KubernetesStorage {
}; };
yield kubeClient.createNamespacedPersistentVolumeClaim(namespace, pvc); yield kubeClient.createNamespacedPersistentVolumeClaim(namespace, pvc);
core.info(`Persistent Volume created, ${yield KubernetesStorage.getPVCPhase(kubeClient, pvcName, namespace)}`); core.info(`Persistent Volume created, ${yield KubernetesStorage.getPVCPhase(kubeClient, pvcName, namespace)}`);
yield this.watchPersistentVolumeClaimUntilBoundToContainer(kubeClient, pvcName, namespace);
core.info(JSON.stringify((yield kubeClient.readNamespacedPersistentVolumeClaimStatus(pvcName, namespace)).body, undefined, 4));
}); });
} }
} }
@ -1788,6 +1838,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
const aws_build_platform_1 = __importDefault(__webpack_require__(70187)); const aws_build_platform_1 = __importDefault(__webpack_require__(70187));
const core = __importStar(__webpack_require__(42186)); const core = __importStar(__webpack_require__(42186));
const remote_builder_namespace_1 = __importDefault(__webpack_require__(96003)); const remote_builder_namespace_1 = __importDefault(__webpack_require__(96003));
const kubernetes_build_platform_1 = __importDefault(__webpack_require__(81730));
const repositoryDirectoryName = 'repo'; const repositoryDirectoryName = 'repo';
const efsDirectoryName = 'data'; const efsDirectoryName = 'data';
const cacheDirectoryName = 'cache'; const cacheDirectoryName = 'cache';
@ -1796,6 +1847,15 @@ class RemoteBuilder {
var _a; var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
switch (buildParameters.remoteBuildCluster) {
case 'aws':
this.RemoteBuilderProviderPlatform = new aws_build_platform_1.default();
break;
case 'k8s':
default:
this.RemoteBuilderProviderPlatform = new kubernetes_build_platform_1.default(buildParameters, baseImage);
break;
}
this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false; this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false;
const buildUid = remote_builder_namespace_1.default.generateBuildName(process.env.GITHUB_RUN_NUMBER, buildParameters.platform); const buildUid = remote_builder_namespace_1.default.generateBuildName(process.env.GITHUB_RUN_NUMBER, buildParameters.platform);
const defaultBranchName = ((_a = process.env.GITHUB_REF) === null || _a === void 0 ? void 0 : _a.split('/').filter((x) => { const defaultBranchName = ((_a = process.env.GITHUB_REF) === null || _a === void 0 ? void 0 : _a.split('/').filter((x) => {
@ -1828,7 +1888,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 aws_build_platform_1.default.runBuild(buildUid, buildParameters.awsStackName, 'alpine/git', [ yield this.RemoteBuilderProviderPlatform.runBuild(buildUid, buildParameters.awsStackName, 'alpine/git', [
'-c', '-c',
`apk update; `apk update;
apk add unzip; apk add unzip;
@ -1940,7 +2000,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 aws_build_platform_1.default.runBuild(buildUid, buildParameters.awsStackName, baseImage.toString(), [ yield this.RemoteBuilderProviderPlatform.runBuild(buildUid, buildParameters.awsStackName, baseImage.toString(), [
'-c', '-c',
` `
cp -r /${efsDirectoryName}/${buildUid}/builder/dist/default-build-script/ /UnityBuilderAction; cp -r /${efsDirectoryName}/${buildUid}/builder/dist/default-build-script/ /UnityBuilderAction;
@ -2010,7 +2070,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 aws_build_platform_1.default.runBuild(buildUid, buildParameters.awsStackName, 'alpine', [ yield this.RemoteBuilderProviderPlatform.runBuild(buildUid, buildParameters.awsStackName, 'alpine', [
'-c', '-c',
` `
apk update apk update
@ -2037,7 +2097,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 aws_build_platform_1.default.runBuild(buildUid, buildParameters.awsStackName, 'amazon/aws-cli', [ yield this.RemoteBuilderProviderPlatform.runBuild(buildUid, buildParameters.awsStackName, '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/
@ -2072,7 +2132,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 aws_build_platform_1.default.runBuild(buildUid, buildParameters.awsStackName, 'cm2network/steamcmd:root', [ yield this.RemoteBuilderProviderPlatform.runBuild(buildUid, buildParameters.awsStackName, '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

@ -7,9 +7,13 @@ import * as core from '@actions/core';
import RemoteBuilderTaskDef from './remote-builder-task-def'; 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';
class AWSBuildEnvironment { class AWSBuildEnvironment implements RemoteBuilderProviderInterface {
static async runBuild( run(): Promise<void> {
throw new Error('Method not implemented.');
}
async runBuild(
buildId: string, buildId: string,
stackName: string, stackName: string,
image: string, image: string,
@ -18,12 +22,12 @@ class AWSBuildEnvironment {
workingdir: string, workingdir: string,
environment: RemoteBuilderEnvironmentVariable[], environment: RemoteBuilderEnvironmentVariable[],
secrets: RemoteBuilderSecret[], secrets: RemoteBuilderSecret[],
) { ): Promise<void> {
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 = await this.setupCloudFormations( const taskDef = await AWSBuildEnvironment.setupCloudFormations(
CF, CF,
buildId, buildId,
stackName, stackName,
@ -37,7 +41,7 @@ class AWSBuildEnvironment {
try { try {
await AWSBuildRunner.runTask(taskDef, ECS, CF, environment, buildId); await AWSBuildRunner.runTask(taskDef, ECS, CF, environment, buildId);
} finally { } finally {
await this.cleanupResources(CF, taskDef); await AWSBuildEnvironment.cleanupResources(CF, taskDef);
} }
} }

View File

@ -7,6 +7,7 @@ import { RemoteBuilderProviderInterface } from './remote-builder-provider-interf
import RemoteBuilderSecret from './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'; import KubernetesStorage from './kubernetes-storage';
import RemoteBuilderEnvironmentVariable from './remote-builder-environment-variable';
const base64 = require('base-64'); const base64 = require('base-64');
@ -41,6 +42,63 @@ class Kubernetes implements RemoteBuilderProviderInterface {
this.setUniqueBuildId(); this.setUniqueBuildId();
} }
async runBuild(
buildId: string,
stackName: string,
image: string,
commands: string[],
mountdir: string,
workingdir: string,
environment: RemoteBuilderEnvironmentVariable[],
secrets: RemoteBuilderSecret[],
): Promise<void> {
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);
try {
// setup
await this.createSecret(defaultSecretsArray);
await KubernetesStorage.createPersistentVolumeClaim(
this.buildParameters,
this.pvcName,
this.kubeClient,
this.namespace,
);
// run
await this.runJob(commands, image);
await this.cleanup();
} catch (error) {
core.error(JSON.stringify(error.response, undefined, 4));
throw error;
}
}
setUniqueBuildId() { setUniqueBuildId() {
const buildId = Kubernetes.uuidv4(); const buildId = Kubernetes.uuidv4();

View File

@ -15,6 +15,26 @@ class KubernetesStorage {
await waitUntil(async () => (await this.getPVCPhase(kubeClient, name, namespace)) !== 'Pending', { await waitUntil(async () => (await this.getPVCPhase(kubeClient, name, namespace)) !== 'Pending', {
timeout: 500000, timeout: 500000,
}); });
core.info(
JSON.stringify((await kubeClient.readNamespacedPersistentVolumeClaimStatus(name, namespace)).body, undefined, 4),
);
const pvc = (await kubeClient.readNamespacedPersistentVolumeClaim(name, namespace)).body;
core.info(JSON.stringify(pvc, undefined, 4));
core.info(
JSON.stringify((await kubeClient.readNamespacedPersistentVolumeClaim(name, namespace)).body, undefined, 4),
);
core.info(
JSON.stringify(
(await kubeClient.replaceNamespacedPersistentVolumeClaimStatus(name, namespace, pvc)).body,
undefined,
4,
),
);
} }
public static async createPersistentVolumeClaim( public static async createPersistentVolumeClaim(
@ -45,14 +65,6 @@ class KubernetesStorage {
}; };
await kubeClient.createNamespacedPersistentVolumeClaim(namespace, pvc); await kubeClient.createNamespacedPersistentVolumeClaim(namespace, pvc);
core.info(`Persistent Volume created, ${await KubernetesStorage.getPVCPhase(kubeClient, pvcName, namespace)}`); core.info(`Persistent Volume created, ${await KubernetesStorage.getPVCPhase(kubeClient, pvcName, namespace)}`);
await this.watchPersistentVolumeClaimUntilBoundToContainer(kubeClient, pvcName, namespace);
core.info(
JSON.stringify(
(await kubeClient.readNamespacedPersistentVolumeClaimStatus(pvcName, namespace)).body,
undefined,
4,
),
);
} }
} }

View File

@ -1,3 +1,24 @@
import RemoteBuilderEnvironmentVariable from './remote-builder-environment-variable';
import RemoteBuilderSecret from './remote-builder-secret';
export interface RemoteBuilderProviderInterface { export interface RemoteBuilderProviderInterface {
run(): Promise<void>; run(): Promise<void>;
runBuild(
// eslint-disable-next-line no-unused-vars
buildId: string,
// eslint-disable-next-line no-unused-vars
stackName: string,
// eslint-disable-next-line no-unused-vars
image: string,
// eslint-disable-next-line no-unused-vars
commands: string[],
// eslint-disable-next-line no-unused-vars
mountdir: string,
// eslint-disable-next-line no-unused-vars
workingdir: string,
// eslint-disable-next-line no-unused-vars
environment: RemoteBuilderEnvironmentVariable[],
// eslint-disable-next-line no-unused-vars
secrets: RemoteBuilderSecret[],
): Promise<void>;
} }

View File

@ -3,14 +3,26 @@ import * as core from '@actions/core';
import { BuildParameters } from '..'; import { BuildParameters } from '..';
import RemoteBuilderNamespace from './remote-builder-namespace'; import RemoteBuilderNamespace from './remote-builder-namespace';
import RemoteBuilderSecret from './remote-builder-secret'; import RemoteBuilderSecret from './remote-builder-secret';
import { RemoteBuilderProviderInterface } from './remote-builder-provider-interface';
import Kubernetes from './kubernetes-build-platform';
const repositoryDirectoryName = 'repo'; const repositoryDirectoryName = 'repo';
const efsDirectoryName = 'data'; const efsDirectoryName = 'data';
const cacheDirectoryName = 'cache'; const cacheDirectoryName = 'cache';
class RemoteBuilder { class RemoteBuilder {
static SteamDeploy: boolean = false; static SteamDeploy: boolean = false;
static RemoteBuilderProviderPlatform: RemoteBuilderProviderInterface;
static async build(buildParameters: BuildParameters, baseImage) { static async build(buildParameters: BuildParameters, baseImage) {
try { try {
switch (buildParameters.remoteBuildCluster) {
case 'aws':
this.RemoteBuilderProviderPlatform = new AWSBuildPlatform();
break;
case 'k8s':
default:
this.RemoteBuilderProviderPlatform = new Kubernetes(buildParameters, baseImage);
break;
}
this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false; this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false;
const buildUid = RemoteBuilderNamespace.generateBuildName( const buildUid = RemoteBuilderNamespace.generateBuildName(
process.env.GITHUB_RUN_NUMBER, process.env.GITHUB_RUN_NUMBER,
@ -53,7 +65,7 @@ class RemoteBuilder {
defaultSecretsArray: RemoteBuilderSecret[], defaultSecretsArray: RemoteBuilderSecret[],
) { ) {
core.info('Starting step 1/4 clone and restore cache)'); core.info('Starting step 1/4 clone and restore cache)');
await AWSBuildPlatform.runBuild( await this.RemoteBuilderProviderPlatform.runBuild(
buildUid, buildUid,
buildParameters.awsStackName, buildParameters.awsStackName,
'alpine/git', 'alpine/git',
@ -188,7 +200,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)');
await AWSBuildPlatform.runBuild( await this.RemoteBuilderProviderPlatform.runBuild(
buildUid, buildUid,
buildParameters.awsStackName, buildParameters.awsStackName,
baseImage.toString(), baseImage.toString(),
@ -271,7 +283,7 @@ class RemoteBuilder {
) { ) {
core.info('Starting step 3/4 build compression'); core.info('Starting step 3/4 build compression');
// Cleanup // Cleanup
await AWSBuildPlatform.runBuild( await this.RemoteBuilderProviderPlatform.runBuild(
buildUid, buildUid,
buildParameters.awsStackName, buildParameters.awsStackName,
'alpine', 'alpine',
@ -311,7 +323,7 @@ class RemoteBuilder {
defaultSecretsArray: RemoteBuilderSecret[], defaultSecretsArray: RemoteBuilderSecret[],
) { ) {
core.info('Starting step 4/4 upload build to s3'); core.info('Starting step 4/4 upload build to s3');
await AWSBuildPlatform.runBuild( await this.RemoteBuilderProviderPlatform.runBuild(
buildUid, buildUid,
buildParameters.awsStackName, buildParameters.awsStackName,
'amazon/aws-cli', 'amazon/aws-cli',
@ -358,7 +370,7 @@ class RemoteBuilder {
defaultSecretsArray: RemoteBuilderSecret[], defaultSecretsArray: RemoteBuilderSecret[],
) { ) {
core.info('Starting steam deployment'); core.info('Starting steam deployment');
await AWSBuildPlatform.runBuild( await this.RemoteBuilderProviderPlatform.runBuild(
buildUid, buildUid,
buildParameters.awsStackName, buildParameters.awsStackName,
'cm2network/steamcmd:root', 'cm2network/steamcmd:root',