unity-builder/src/model/cloud-runner/cloud-runner.ts

411 lines
14 KiB
TypeScript
Raw Normal View History

2021-08-01 23:43:20 +00:00
import AWSBuildPlatform from './aws-build-platform';
import * as core from '@actions/core';
import { BuildParameters } from '..';
2021-08-17 22:13:46 +00:00
import CloudRunnerNamespace from './cloud-runner-namespace';
import CloudRunnerSecret from './cloud-runner-secret';
2021-08-17 20:09:42 +00:00
import { CloudRunnerProviderInterface } from './cloud-runner-provider-interface';
2021-08-01 23:43:20 +00:00
import Kubernetes from './kubernetes-build-platform';
2021-08-17 22:13:46 +00:00
import CloudRunnerEnvironmentVariable from './cloud-runner-environment-variable';
import ImageEnvironmentFactory from '../image-environment-factory';
2021-08-15 21:03:29 +00:00
import YAML from 'yaml';
2021-08-21 23:26:03 +00:00
import CloudRunnerTimerLogger from './cloud-runner-timer-logger';
2021-08-01 23:43:20 +00:00
const repositoryFolder = 'repo';
const buildVolumeFolder = 'data';
const cacheFolder = 'cache';
2021-08-17 20:09:42 +00:00
class CloudRunner {
2021-08-17 22:13:46 +00:00
static CloudRunnerProviderPlatform: CloudRunnerProviderInterface;
2021-08-01 23:43:20 +00:00
private static buildParams: BuildParameters;
2021-08-17 22:13:46 +00:00
private static defaultSecrets: CloudRunnerSecret[];
private static buildGuid: string;
2021-08-01 23:43:20 +00:00
private static branchName: string;
private static buildPathFull: string;
private static builderPathFull: string;
private static steamPathFull: string;
private static repoPathFull: string;
private static projectPathFull: string;
private static libraryFolderFull: string;
2021-08-15 21:32:43 +00:00
private static cacheFolderFull: string;
2021-08-21 19:01:53 +00:00
private static lfsDirectory: string;
private static purgeRemoteCaching: boolean;
private static CloudRunnerBranch: string;
private static unityBuilderRepoUrl: string;
private static targetBuildRepoUrl: string;
2021-08-01 23:43:20 +00:00
private static readonly defaultGitShaEnvironmentVariable = [
{
name: 'GITHUB_SHA',
value: process.env.GITHUB_SHA || '',
},
];
2021-08-21 23:26:03 +00:00
private static setup(buildParameters: BuildParameters) {
CloudRunnerTimerLogger.setup();
2021-08-17 22:13:46 +00:00
CloudRunner.buildGuid = CloudRunnerNamespace.generateBuildName(
2021-08-17 20:09:42 +00:00
CloudRunner.readRunNumber(),
2021-08-01 23:43:20 +00:00
buildParameters.platform,
);
2021-08-17 20:09:42 +00:00
CloudRunner.buildParams = buildParameters;
CloudRunner.setupBranchName();
CloudRunner.setupFolderVariables();
CloudRunner.setupDefaultSecrets();
2021-08-21 23:26:03 +00:00
CloudRunner.setupBuildPlatform();
2021-08-01 23:43:20 +00:00
}
private static setupFolderVariables() {
this.buildPathFull = `/${buildVolumeFolder}/${this.buildGuid}`;
this.builderPathFull = `${this.buildPathFull}/builder`;
this.steamPathFull = `${this.buildPathFull}/steam`;
this.repoPathFull = `${this.buildPathFull}/${repositoryFolder}`;
this.projectPathFull = `${this.repoPathFull}/${this.buildParams.projectPath}`;
this.libraryFolderFull = `${this.projectPathFull}/Library`;
2021-08-15 21:32:43 +00:00
this.cacheFolderFull = `/${buildVolumeFolder}/${cacheFolder}/${this.branchName}`;
2021-08-21 19:01:53 +00:00
this.lfsDirectory = `${this.repoPathFull}/.git/lfs`;
this.purgeRemoteCaching = process.env.PURGE_REMOTE_BUILDER_CACHE !== undefined;
this.CloudRunnerBranch = process.env.CloudRunnerBranch ? `--branch "${process.env.CloudRunnerBranch}"` : '';
this.unityBuilderRepoUrl = `https://${this.buildParams.githubToken}@github.com/game-ci/unity-builder.git`;
this.targetBuildRepoUrl = `https://${this.buildParams.githubToken}@github.com/${process.env.GITHUB_REPOSITORY}.git`;
}
2021-08-21 19:01:53 +00:00
private static getHandleCachingCommand() {
return `${this.builderPathFull}/dist/cloud-runner/handleCaching.sh "${this.cacheFolderFull}" "${this.libraryFolderFull}" "${this.lfsDirectory}" "${this.purgeRemoteCaching}"`;
}
2021-08-01 23:43:20 +00:00
2021-08-21 19:01:53 +00:00
private static getCloneNoLFSCommand() {
2021-08-21 19:49:52 +00:00
return `${this.builderPathFull}/dist/cloud-runner/cloneNoLFS.sh "${this.repoPathFull}" "${this.targetBuildRepoUrl}"`;
2021-08-21 19:01:53 +00:00
}
2021-08-01 23:43:20 +00:00
2021-08-21 19:01:53 +00:00
private static getCloneBuilder() {
return `git clone -q ${this.CloudRunnerBranch} ${this.unityBuilderRepoUrl} ${this.builderPathFull}`;
}
2021-08-01 23:43:20 +00:00
2021-08-21 23:26:03 +00:00
static async run(buildParameters: BuildParameters, baseImage) {
CloudRunner.setup(buildParameters);
try {
await CloudRunner.setupSharedBuildResources();
await CloudRunner.setupStep();
await CloudRunner.runMainJob(baseImage);
await CloudRunner.cleanupSharedBuildResources();
} catch (error) {
await CloudRunner.handleException(error);
throw error;
}
2021-08-01 23:43:20 +00:00
}
2021-08-21 23:26:03 +00:00
private static async setupSharedBuildResources() {
await this.CloudRunnerProviderPlatform.setupSharedBuildResources(
this.buildGuid,
2021-08-21 23:26:03 +00:00
this.buildParams,
this.branchName,
2021-08-01 23:43:20 +00:00
this.defaultSecrets,
);
}
private static setupBuildPlatform() {
2021-08-17 22:13:46 +00:00
switch (this.buildParams.cloudRunnerCluster) {
2021-08-01 23:43:20 +00:00
case 'aws':
core.info('Building with AWS');
2021-08-17 22:13:46 +00:00
this.CloudRunnerProviderPlatform = new AWSBuildPlatform(this.buildParams);
2021-08-01 23:43:20 +00:00
break;
default:
case 'k8s':
core.info('Building with Kubernetes');
2021-08-17 22:13:46 +00:00
this.CloudRunnerProviderPlatform = new Kubernetes(this.buildParams);
2021-08-01 23:43:20 +00:00
break;
}
}
private static readRunNumber() {
const runNumber = process.env.GITHUB_RUN_NUMBER;
if (!runNumber || runNumber === '') {
throw new Error('no run number found, exiting');
}
return runNumber;
}
private static setupBranchName() {
2021-08-01 23:43:20 +00:00
const defaultBranchName =
process.env.GITHUB_REF?.split('/')
.filter((x) => {
x = x[0].toUpperCase() + x.slice(1);
return x;
})
.join('') || '';
this.branchName =
process.env.REMOTE_BUILDER_CACHE !== undefined ? process.env.REMOTE_BUILDER_CACHE : defaultBranchName;
}
private static setupDefaultSecrets() {
2021-08-01 23:43:20 +00:00
this.defaultSecrets = [
{
ParameterKey: 'GithubToken',
EnvironmentVariable: 'GITHUB_TOKEN',
ParameterValue: this.buildParams.githubToken,
},
2021-08-15 13:46:18 +00:00
{
ParameterKey: 'branch',
EnvironmentVariable: 'branch',
ParameterValue: this.branchName,
},
2021-08-15 13:58:44 +00:00
{
ParameterKey: 'buildPathFull',
EnvironmentVariable: 'buildPathFull',
ParameterValue: this.buildPathFull,
},
{
ParameterKey: 'projectPathFull',
EnvironmentVariable: 'projectPathFull',
ParameterValue: this.projectPathFull,
},
{
ParameterKey: 'libraryFolderFull',
EnvironmentVariable: 'libraryFolderFull',
ParameterValue: this.libraryFolderFull,
},
{
ParameterKey: 'builderPathFull',
EnvironmentVariable: 'builderPathFull',
ParameterValue: this.builderPathFull,
},
{
ParameterKey: 'repoPathFull',
EnvironmentVariable: 'repoPathFull',
ParameterValue: this.repoPathFull,
},
{
ParameterKey: 'steamPathFull',
EnvironmentVariable: 'steamPathFull',
ParameterValue: this.steamPathFull,
},
2021-08-01 23:43:20 +00:00
];
this.defaultSecrets.push(
...ImageEnvironmentFactory.getEnvironmentVariables(this.buildParams).map((x) => {
return {
ParameterKey: x.name,
EnvironmentVariable: x.name,
ParameterValue: x.value,
};
}),
);
2021-08-01 23:43:20 +00:00
}
2021-08-17 22:13:46 +00:00
private static readBuildEnvironmentVariables(): CloudRunnerEnvironmentVariable[] {
2021-08-01 23:43:20 +00:00
return [
{
name: 'ContainerMemory',
2021-08-17 22:13:46 +00:00
value: this.buildParams.cloudRunnerMemory,
2021-08-01 23:43:20 +00:00
},
{
name: 'ContainerCpu',
2021-08-17 22:13:46 +00:00
value: this.buildParams.cloudRunnerCpu,
2021-08-01 23:43:20 +00:00
},
{
name: 'GITHUB_WORKSPACE',
value: `/${buildVolumeFolder}/${this.buildGuid}/${repositoryFolder}/`,
2021-08-01 23:43:20 +00:00
},
{
name: 'PROJECT_PATH',
value: this.buildParams.projectPath,
},
{
name: 'BUILD_PATH',
value: this.buildParams.buildPath,
},
{
name: 'BUILD_FILE',
value: this.buildParams.buildFile,
},
{
name: 'BUILD_NAME',
value: this.buildParams.buildName,
},
{
name: 'BUILD_METHOD',
value: this.buildParams.buildMethod,
},
{
name: 'CUSTOM_PARAMETERS',
value: this.buildParams.customParameters,
},
{
name: 'BUILD_TARGET',
value: this.buildParams.platform,
},
{
name: 'ANDROID_VERSION_CODE',
value: this.buildParams.androidVersionCode.toString(),
},
{
name: 'ANDROID_KEYSTORE_NAME',
value: this.buildParams.androidKeystoreName,
},
{
name: 'ANDROID_KEYALIAS_NAME',
value: this.buildParams.androidKeyaliasName,
},
];
}
2021-08-21 23:26:03 +00:00
private static async runMainJob(baseImage: any) {
if (!this.buildParams.customBuildSteps) {
core.info(`Cloud Runner is running in standard build automation mode`);
await CloudRunner.standardBuildAutomation(baseImage);
} else {
core.info(`Cloud Runner is running in custom job mode`);
await CloudRunner.runCustomJob(this.buildParams.customBuildSteps);
}
}
private static async standardBuildAutomation(baseImage: any) {
CloudRunnerTimerLogger.logWithTime('Pre build steps time');
await this.runCustomJob(this.buildParams.preBuildSteps);
CloudRunnerTimerLogger.logWithTime('Setup time');
await CloudRunner.BuildStep(baseImage);
CloudRunnerTimerLogger.logWithTime('Build time');
await CloudRunner.CompressionStep();
CloudRunnerTimerLogger.logWithTime('Compression time');
await this.runCustomJob(this.buildParams.postBuildSteps);
CloudRunnerTimerLogger.logWithTime('Post build steps time');
}
private static async runCustomJob(buildSteps) {
buildSteps = YAML.parse(buildSteps);
for (const step of buildSteps) {
const stepSecrets: CloudRunnerSecret[] = step.secrets.map((x) => {
const secret: CloudRunnerSecret = {
ParameterKey: x.name,
EnvironmentVariable: x.name,
ParameterValue: x.value,
};
return secret;
});
await this.CloudRunnerProviderPlatform.runBuildTask(
this.buildGuid,
step['image'],
step['commands'],
`/${buildVolumeFolder}`,
`/${buildVolumeFolder}`,
2021-08-21 23:29:27 +00:00
this.defaultGitShaEnvironmentVariable,
2021-08-21 23:26:03 +00:00
[...this.defaultSecrets, ...stepSecrets],
);
}
}
private static async setupStep() {
core.info('Starting step 1/4 clone and restore cache)');
await this.CloudRunnerProviderPlatform.runBuildTask(
this.buildGuid,
'alpine/git',
[
` printenv
apk update -q
apk add unzip zip git-lfs jq tree -q
mkdir -p ${this.buildPathFull}
mkdir -p ${this.builderPathFull}
mkdir -p ${this.repoPathFull}
${this.getCloneBuilder()}
echo ' '
echo 'Initializing source repository for cloning with caching of LFS files'
${this.getCloneNoLFSCommand()}
echo 'Source repository initialized'
echo ' '
echo 'Starting checks of cache for the Unity project Library and git LFS files'
${this.getHandleCachingCommand()}
`,
],
`/${buildVolumeFolder}`,
`/${buildVolumeFolder}/`,
CloudRunner.defaultGitShaEnvironmentVariable,
this.defaultSecrets,
);
}
private static async BuildStep(baseImage: any) {
core.info('Starting part 2/4 (build unity project)');
await this.CloudRunnerProviderPlatform.runBuildTask(
this.buildGuid,
baseImage.toString(),
[
`
printenv
export GITHUB_WORKSPACE="${this.repoPathFull}"
cp -r "${this.builderPathFull}/dist/default-build-script/" "/UnityBuilderAction"
cp -r "${this.builderPathFull}/dist/entrypoint.sh" "/entrypoint.sh"
cp -r "${this.builderPathFull}/dist/steps/" "/steps"
chmod -R +x "/entrypoint.sh"
chmod -R +x "/steps"
/entrypoint.sh
${process.env.DEBUG ? '' : '#'}tree -L 4 "${this.buildPathFull}"
${process.env.DEBUG ? '' : '#'}ls -lh "/${buildVolumeFolder}"
`,
],
`/${buildVolumeFolder}`,
`/${this.projectPathFull}`,
CloudRunner.readBuildEnvironmentVariables(),
this.defaultSecrets,
);
}
private static async CompressionStep() {
core.info('Starting step 3/4 build compression');
// Cleanup
await this.CloudRunnerProviderPlatform.runBuildTask(
this.buildGuid,
'alpine',
[
`
printenv
apk update -q
apk add zip tree -q
${process.env.DEBUG ? '' : '#'}tree -L 4 "$repoPathFull"
${process.env.DEBUG ? '' : '#'}ls -lh "$repoPathFull"
cd "$libraryFolderFull/.."
zip -r "lib-$BUILDID.zip" "./Library"
mv "lib-$BUILDID.zip" "/$cacheFolderFull/lib"
cd "$repoPathFull"
ls -lh "$repoPathFull"
zip -r "build-$BUILDID.zip" "./${CloudRunner.buildParams.buildPath}"
mv "build-$BUILDID.zip" "/$cacheFolderFull/build-$BUILDID.zip"
${process.env.DEBUG ? '' : '#'}tree -L 4 "/$cacheFolderFull"
${process.env.DEBUG ? '' : '#'}tree -L 4 "/$cacheFolderFull/.."
${process.env.DEBUG ? '' : '#'}tree -L 4 "$repoPathFull"
${process.env.DEBUG ? '' : '#'}ls -lh "$repoPathFull"
`,
],
`/${buildVolumeFolder}`,
`/${buildVolumeFolder}`,
[
2021-08-21 23:29:27 +00:00
...CloudRunner.defaultGitShaEnvironmentVariable,
...[
{
name: 'cacheFolderFull',
value: this.cacheFolderFull,
},
],
2021-08-21 23:26:03 +00:00
],
this.defaultSecrets,
);
core.info('compression step complete');
}
private static async cleanupSharedBuildResources() {
await this.CloudRunnerProviderPlatform.cleanupSharedBuildResources(
this.buildGuid,
this.buildParams,
this.branchName,
this.defaultSecrets,
);
}
2021-08-01 23:43:20 +00:00
private static async handleException(error: unknown) {
core.error(JSON.stringify(error, undefined, 4));
core.setFailed('Remote Builder failed');
2021-08-17 22:13:46 +00:00
await this.CloudRunnerProviderPlatform.cleanupSharedBuildResources(
this.buildGuid,
2021-08-01 23:43:20 +00:00
this.buildParams,
this.branchName,
this.defaultSecrets,
);
}
}
2021-08-17 20:09:42 +00:00
export default CloudRunner;