mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-02-03 23:49:08 +08:00
triggerWorkflowOnComplete param for cloud runner
This commit is contained in:
@@ -62,6 +62,9 @@ class CloudRunnerOptions {
|
||||
static get githubChecks(): boolean {
|
||||
return CloudRunnerOptions.getInput('githubChecks') || false;
|
||||
}
|
||||
static get githubCheckId(): string {
|
||||
return CloudRunnerOptions.getInput('githubCheckId') || ``;
|
||||
}
|
||||
|
||||
static get githubOwner() {
|
||||
return CloudRunnerOptions.getInput('githubOwner') || CloudRunnerOptions.githubRepo.split(`/`)[0] || false;
|
||||
@@ -71,6 +74,10 @@ class CloudRunnerOptions {
|
||||
return CloudRunnerOptions.getInput('githubRepoName') || CloudRunnerOptions.githubRepo.split(`/`)[1] || false;
|
||||
}
|
||||
|
||||
static get triggerWorkflowOnComplete() {
|
||||
return CloudRunnerOptions.getInput('triggerWorkflowOnComplete')?.split(',') || [];
|
||||
}
|
||||
|
||||
// ### ### ###
|
||||
// Git syncronization parameters
|
||||
// ### ### ###
|
||||
@@ -242,7 +249,7 @@ class CloudRunnerOptions {
|
||||
return CloudRunnerOptions.getInput(`watchToEnd`) || true;
|
||||
}
|
||||
|
||||
static get asyncCloudRunner(): boolean {
|
||||
public static get asyncCloudRunner(): boolean {
|
||||
return (CloudRunnerOptions.getInput('asyncCloudRunner') || `false`) === `true` || false;
|
||||
}
|
||||
|
||||
@@ -251,7 +258,7 @@ class CloudRunnerOptions {
|
||||
}
|
||||
|
||||
public static get useSharedBuilder(): boolean {
|
||||
return (CloudRunnerOptions.getInput(`useSharedBuilder`) || 'true') === 'true';
|
||||
return (CloudRunnerOptions.getInput(`useSharedBuilder`) || 'false') === 'true';
|
||||
}
|
||||
|
||||
public static get useLz4Compression(): boolean {
|
||||
|
||||
@@ -23,11 +23,19 @@ class CloudRunner {
|
||||
private static cloudRunnerEnvironmentVariables: CloudRunnerEnvironmentVariable[];
|
||||
static lockedWorkspace: string | undefined;
|
||||
public static readonly retainedWorkspacePrefix: string = `retained-workspace`;
|
||||
public static githubCheckId;
|
||||
public static setup(buildParameters: BuildParameters) {
|
||||
public static get isCloudRunnerEnvironment() {
|
||||
return process.env[`GITHUB_ACTIONS`] !== `true`;
|
||||
}
|
||||
public static get isCloudRunnerAsyncEnvironment() {
|
||||
return process.env[`GAMECI_ASYNC_WORKFLOW`] === `true`;
|
||||
}
|
||||
public static async setup(buildParameters: BuildParameters) {
|
||||
CloudRunnerLogger.setup();
|
||||
CloudRunnerLogger.log(`Setting up cloud runner`);
|
||||
CloudRunner.buildParameters = buildParameters;
|
||||
if (CloudRunner.buildParameters.githubCheckId === ``) {
|
||||
CloudRunner.buildParameters.githubCheckId = await GitHub.createGitHubCheck(CloudRunner.buildParameters.buildGuid);
|
||||
}
|
||||
CloudRunner.setupSelectedBuildPlatform();
|
||||
CloudRunner.defaultSecrets = TaskParameterSerializer.readDefaultSecrets();
|
||||
CloudRunner.cloudRunnerEnvironmentVariables =
|
||||
@@ -73,10 +81,8 @@ class CloudRunner {
|
||||
}
|
||||
|
||||
static async run(buildParameters: BuildParameters, baseImage: string) {
|
||||
CloudRunner.setup(buildParameters);
|
||||
await CloudRunner.setup(buildParameters);
|
||||
try {
|
||||
CloudRunner.githubCheckId = await GitHub.createGitHubCheck(CloudRunner.buildParameters.buildGuid);
|
||||
|
||||
if (buildParameters.retainWorkspace) {
|
||||
CloudRunner.lockedWorkspace = `${CloudRunner.retainedWorkspacePrefix}-${CloudRunner.buildParameters.buildGuid}`;
|
||||
|
||||
@@ -106,7 +112,11 @@ class CloudRunner {
|
||||
CloudRunner.defaultSecrets,
|
||||
);
|
||||
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
|
||||
await GitHub.updateGitHubCheck(CloudRunner.buildParameters.buildGuid, CloudRunner.buildParameters.buildGuid);
|
||||
const content = { ...CloudRunner.buildParameters };
|
||||
content.gitPrivateToken = ``;
|
||||
content.unitySerial = ``;
|
||||
const jsonContent = JSON.stringify(content, undefined, 4);
|
||||
await GitHub.updateGitHubCheck(jsonContent, CloudRunner.buildParameters.buildGuid);
|
||||
const output = await new WorkflowCompositionRoot().run(
|
||||
new CloudRunnerStepState(baseImage, CloudRunner.cloudRunnerEnvironmentVariables, CloudRunner.defaultSecrets),
|
||||
);
|
||||
@@ -130,6 +140,8 @@ class CloudRunner {
|
||||
CloudRunner.lockedWorkspace = undefined;
|
||||
}
|
||||
|
||||
await GitHub.triggerWorkflowOnComplete(CloudRunner.buildParameters.triggerWorkflowOnComplete);
|
||||
|
||||
if (buildParameters.constantGarbageCollection) {
|
||||
CloudRunner.Provider.garbageCollect(``, true, buildParameters.garbageCollectionMaxAge, true, true);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,9 @@ class AWSTaskRunner {
|
||||
const containerState = taskData.containers?.[0];
|
||||
const exitCode = containerState?.exitCode || undefined;
|
||||
CloudRunnerLogger.log(`Container State: ${JSON.stringify(containerState, undefined, 4)}`);
|
||||
if (exitCode === undefined) {
|
||||
CloudRunnerLogger.logWarning(`No exitcode for container`);
|
||||
}
|
||||
const wasSuccessful = exitCode === 0 || (exitCode === undefined && taskData.lastStatus === 'RUNNING');
|
||||
if (wasSuccessful) {
|
||||
CloudRunnerLogger.log(`Cloud runner job has finished successfully`);
|
||||
|
||||
@@ -129,14 +129,27 @@ class Kubernetes implements ProviderInterface {
|
||||
this.jobName = `unity-builder-job-${this.buildGuid}`;
|
||||
this.containerName = `main`;
|
||||
await KubernetesSecret.createSecret(secrets, this.secretName, this.namespace, this.kubeClient);
|
||||
await this.createNamespacedJob(commands, image, mountdir, workingdir, environment, secrets);
|
||||
this.setPodNameAndContainerName(await Kubernetes.findPodFromJob(this.kubeClient, this.jobName, this.namespace));
|
||||
CloudRunnerLogger.log('Watching pod until running');
|
||||
await KubernetesTaskRunner.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
|
||||
let output = '';
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
let existsAlready = false;
|
||||
let status;
|
||||
try {
|
||||
status = await this.kubeClient.readNamespacedPodStatus(this.podName, this.namespace);
|
||||
CloudRunnerLogger.log(JSON.stringify(status.body.status?.containerStatuses, undefined, 4));
|
||||
existsAlready = true;
|
||||
} catch {
|
||||
// empty
|
||||
}
|
||||
if (!existsAlready || status.state?.terminated !== undefined) {
|
||||
CloudRunnerLogger.log('Job does not exist');
|
||||
await this.createNamespacedJob(commands, image, mountdir, workingdir, environment, secrets);
|
||||
const find = await Kubernetes.findPodFromJob(this.kubeClient, this.jobName, this.namespace);
|
||||
this.setPodNameAndContainerName(find);
|
||||
CloudRunnerLogger.log('Watching pod until running');
|
||||
await KubernetesTaskRunner.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
|
||||
}
|
||||
CloudRunnerLogger.log('Pod running, streaming logs');
|
||||
output = await KubernetesTaskRunner.runTask(
|
||||
this.kubeConfig,
|
||||
@@ -163,11 +176,12 @@ class Kubernetes implements ProviderInterface {
|
||||
errorParsed = error;
|
||||
}
|
||||
|
||||
const reason = errorParsed.reason || errorParsed.response?.body?.reason || ``;
|
||||
const errorMessage = errorParsed.message || reason;
|
||||
const errorMessage =
|
||||
errorParsed.name || errorParsed.reason || errorParsed.response?.body?.reason || errorParsed.message;
|
||||
|
||||
const continueStreaming =
|
||||
errorMessage.includes(`dial timeout, backstop`) ||
|
||||
errorMessage.includes(`HttpError`) ||
|
||||
errorMessage.includes(`HttpError: HTTP request failed`) ||
|
||||
errorMessage.includes(`an error occurred when try to find container`) ||
|
||||
errorMessage.includes(`not found`) ||
|
||||
@@ -192,6 +206,18 @@ class Kubernetes implements ProviderInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private async doesJobExist(name) {
|
||||
const jobs = await this.kubeClientBatch.listNamespacedJob(this.namespace);
|
||||
|
||||
return jobs.body.items.some((x) => x.metadata?.name === name);
|
||||
}
|
||||
|
||||
private async doesFailedJobExist() {
|
||||
const podStatus = await this.kubeClient.readNamespacedPodStatus(this.podName, this.namespace);
|
||||
|
||||
return podStatus.body.status?.phase === `Failed`;
|
||||
}
|
||||
|
||||
private async createNamespacedJob(
|
||||
commands: string,
|
||||
image: string,
|
||||
@@ -217,12 +243,12 @@ class Kubernetes implements ProviderInterface {
|
||||
k8s,
|
||||
);
|
||||
await new Promise((promise) => setTimeout(promise, 15000));
|
||||
await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
|
||||
const result = await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
|
||||
CloudRunnerLogger.log(`Build job created`);
|
||||
await new Promise((promise) => setTimeout(promise, 5000));
|
||||
CloudRunnerLogger.log('Job created');
|
||||
|
||||
return;
|
||||
return result.body.metadata?.name;
|
||||
} catch (error) {
|
||||
CloudRunnerLogger.log(`Error occured creating job: ${error}`);
|
||||
throw error;
|
||||
|
||||
@@ -7,6 +7,7 @@ import waitUntil from 'async-wait-until';
|
||||
import { FollowLogStreamService } from '../../services/follow-log-stream-service';
|
||||
|
||||
class KubernetesTaskRunner {
|
||||
static lastReceivedTimestamp: number;
|
||||
static async runTask(
|
||||
kubeConfig: KubeConfig,
|
||||
kubeClient: CoreV1Api,
|
||||
@@ -33,15 +34,51 @@ class KubernetesTaskRunner {
|
||||
));
|
||||
next();
|
||||
};
|
||||
|
||||
// export interface LogOptions {
|
||||
/**
|
||||
* Follow the log stream of the pod. Defaults to false.
|
||||
*/
|
||||
// follow?: boolean;
|
||||
/**
|
||||
* If set, the number of bytes to read from the server before terminating the log output. This may not display a
|
||||
* complete final line of logging, and may return slightly more or slightly less than the specified limit.
|
||||
*/
|
||||
// limitBytes?: number;
|
||||
/**
|
||||
* If true, then the output is pretty printed.
|
||||
*/
|
||||
// pretty?: boolean;
|
||||
/**
|
||||
* Return previous terminated container logs. Defaults to false.
|
||||
*/
|
||||
// previous?: boolean;
|
||||
/**
|
||||
* A relative time in seconds before the current time from which to show logs. If this value precedes the time a
|
||||
* pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will
|
||||
* be returned. Only one of sinceSeconds or sinceTime may be specified.
|
||||
*/
|
||||
// sinceSeconds?: number;
|
||||
/**
|
||||
* If set, the number of lines from the end of the logs to show. If not specified, logs are shown from the creation
|
||||
* of the container or sinceSeconds or sinceTime
|
||||
*/
|
||||
// tailLines?: number;
|
||||
/**
|
||||
* If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line of log output. Defaults to false.
|
||||
*/
|
||||
// timestamps?: boolean;
|
||||
// }
|
||||
|
||||
const logOptions = {
|
||||
follow: true,
|
||||
pretty: false,
|
||||
previous: false,
|
||||
previous: true,
|
||||
timestamps: true,
|
||||
sinceSeconds: KubernetesTaskRunner.lastReceivedTimestamp,
|
||||
};
|
||||
try {
|
||||
const resultError = await new Promise((resolve) =>
|
||||
new Log(kubeConfig).log(namespace, podName, containerName, stream, resolve, logOptions),
|
||||
);
|
||||
const resultError = await new Log(kubeConfig).log(namespace, podName, containerName, stream, logOptions);
|
||||
stream.destroy();
|
||||
if (resultError) {
|
||||
throw resultError;
|
||||
@@ -73,6 +110,8 @@ class KubernetesTaskRunner {
|
||||
if (stream) {
|
||||
stream.destroy();
|
||||
}
|
||||
CloudRunnerLogger.log(JSON.stringify(error));
|
||||
CloudRunnerLogger.log('k8s task runner failed');
|
||||
throw error;
|
||||
}
|
||||
CloudRunnerLogger.log('end of log stream');
|
||||
|
||||
@@ -10,6 +10,7 @@ import CloudRunnerLogger from '../services/cloud-runner-logger';
|
||||
import { CliFunction } from '../../cli/cli-functions-repository';
|
||||
import { CloudRunnerSystem } from '../services/cloud-runner-system';
|
||||
import YAML from 'yaml';
|
||||
import GitHub from '../../github';
|
||||
|
||||
export class RemoteClient {
|
||||
public static async bootstrapRepository() {
|
||||
@@ -20,7 +21,7 @@ export class RemoteClient {
|
||||
);
|
||||
process.chdir(CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute));
|
||||
await RemoteClient.cloneRepoWithoutLFSFiles();
|
||||
RemoteClient.replaceLargePackageReferencesWithSharedReferences();
|
||||
await RemoteClient.replaceLargePackageReferencesWithSharedReferences();
|
||||
await RemoteClient.sizeOfFolder(
|
||||
'repo before lfs cache pull',
|
||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute),
|
||||
@@ -95,7 +96,12 @@ export class RemoteClient {
|
||||
assert(fs.existsSync(`.git`), 'git folder exists');
|
||||
RemoteClientLogger.log(`${CloudRunner.buildParameters.branch}`);
|
||||
await CloudRunnerSystem.Run(`git checkout ${CloudRunner.buildParameters.branch}`);
|
||||
await CloudRunnerSystem.Run(`git checkout ${CloudRunner.buildParameters.gitSha}`);
|
||||
if (CloudRunner.buildParameters.gitSha !== undefined) {
|
||||
await CloudRunnerSystem.Run(`git checkout ${CloudRunner.buildParameters.gitSha}`);
|
||||
} else {
|
||||
RemoteClientLogger.log(`buildParameter Git Sha is empty`);
|
||||
}
|
||||
|
||||
assert(fs.existsSync(path.join(`.git`, `lfs`)), 'LFS folder should not exist before caching');
|
||||
RemoteClientLogger.log(`Checked out ${CloudRunner.buildParameters.branch}`);
|
||||
} catch (error) {
|
||||
@@ -104,16 +110,17 @@ export class RemoteClient {
|
||||
}
|
||||
}
|
||||
|
||||
static replaceLargePackageReferencesWithSharedReferences() {
|
||||
static async replaceLargePackageReferencesWithSharedReferences() {
|
||||
CloudRunnerLogger.log(`Use Shared Pkgs ${CloudRunner.buildParameters.useSharedLargePackages}`);
|
||||
GitHub.updateGitHubCheck(`Use Shared Pkgs ${CloudRunner.buildParameters.useSharedLargePackages}`, ``);
|
||||
if (CloudRunner.buildParameters.useSharedLargePackages) {
|
||||
await CloudRunnerSystem.Run(`tree -L 2 ${CloudRunnerFolders.projectPathAbsolute}`);
|
||||
const filePath = path.join(CloudRunnerFolders.projectPathAbsolute, `Packages/manifest.json`);
|
||||
let manifest = fs.readFileSync(filePath, 'utf8');
|
||||
manifest = manifest.replace(/LargeContent/g, '../../../LargeContent');
|
||||
fs.writeFileSync(filePath, manifest);
|
||||
if (CloudRunner.buildParameters.cloudRunnerDebug) {
|
||||
CloudRunnerLogger.log(`Package Manifest`);
|
||||
CloudRunnerLogger.log(manifest);
|
||||
}
|
||||
CloudRunnerLogger.log(`Package Manifest \n ${manifest}`);
|
||||
GitHub.updateGitHubCheck(`Package Manifest \n ${manifest}`, ``);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +163,6 @@ export class RemoteClient {
|
||||
if (!CloudRunner.buildParameters.retainWorkspace) {
|
||||
return;
|
||||
}
|
||||
RemoteClientLogger.log(`Retained Workspace: ${CloudRunner.lockedWorkspace}`);
|
||||
RemoteClientLogger.log(`Retained Workspace: ${CloudRunner.lockedWorkspace !== undefined}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CloudRunnerStatics } from '../cloud-runner-statics';
|
||||
import GitHub from '../../github';
|
||||
|
||||
export class FollowLogStreamService {
|
||||
static errors = ``;
|
||||
public static handleIteration(message, shouldReadLogs, shouldCleanup, output) {
|
||||
if (message.includes(`---${CloudRunner.buildParameters.logId}`)) {
|
||||
CloudRunnerLogger.log('End of log transmission received');
|
||||
@@ -17,10 +18,27 @@ export class FollowLogStreamService {
|
||||
GitHub.updateGitHubCheck(`Build succeeded`, `Build succeeded`);
|
||||
core.setOutput('build-result', 'success');
|
||||
} else if (message.includes('Build fail')) {
|
||||
GitHub.updateGitHubCheck(`Build failed`, `Build failed`);
|
||||
GitHub.updateGitHubCheck(
|
||||
`Build failed\n${FollowLogStreamService.errors}`,
|
||||
`Build failed`,
|
||||
`failure`,
|
||||
`completed`,
|
||||
);
|
||||
core.setOutput('build-result', 'failed');
|
||||
core.setFailed('unity build failed');
|
||||
core.error('BUILD FAILED!');
|
||||
} else if (message.toLowerCase().includes('error ')) {
|
||||
FollowLogStreamService.errors += `\n${message}`;
|
||||
} else if (message.toLowerCase().includes('command failed: ')) {
|
||||
FollowLogStreamService.errors += `\n${message}`;
|
||||
} else if (message.toLowerCase().includes('error: ')) {
|
||||
FollowLogStreamService.errors += `\n${message}`;
|
||||
} else if (message.toLowerCase().includes('invalid ')) {
|
||||
FollowLogStreamService.errors += `\n${message}`;
|
||||
} else if (message.toLowerCase().includes('incompatible ')) {
|
||||
FollowLogStreamService.errors += `\n${message}`;
|
||||
} else if (message.toLowerCase().includes('cannot be found')) {
|
||||
FollowLogStreamService.errors += `\n${message}`;
|
||||
} else if (CloudRunner.buildParameters.cloudRunnerDebug && message.includes(': Listening for Jobs')) {
|
||||
core.setOutput('cloud runner stop watching', 'true');
|
||||
shouldReadLogs = false;
|
||||
|
||||
@@ -11,6 +11,9 @@ export class AsyncWorkflow {
|
||||
): Promise<string> {
|
||||
try {
|
||||
CloudRunnerLogger.log(`Cloud Runner is running async mode`);
|
||||
const asyncEnvironmentVariable = new CloudRunnerEnvironmentVariable();
|
||||
asyncEnvironmentVariable.name = `GAMECI_ASYNC_WORKFLOW`;
|
||||
asyncEnvironmentVariable.value = `true`;
|
||||
|
||||
let output = '';
|
||||
|
||||
@@ -34,7 +37,7 @@ aws --version
|
||||
node /builder/dist/index.js -m async-workflow`,
|
||||
`/${CloudRunnerFolders.buildVolumeFolder}`,
|
||||
`/${CloudRunnerFolders.buildVolumeFolder}/`,
|
||||
environmentVariables,
|
||||
[...environmentVariables, asyncEnvironmentVariable],
|
||||
[
|
||||
...secrets,
|
||||
...[
|
||||
|
||||
@@ -9,7 +9,11 @@ import { AsyncWorkflow } from './async-workflow';
|
||||
export class WorkflowCompositionRoot implements WorkflowInterface {
|
||||
async run(cloudRunnerStepState: CloudRunnerStepState) {
|
||||
try {
|
||||
if (CloudRunnerOptions.asyncCloudRunner) {
|
||||
if (
|
||||
CloudRunnerOptions.asyncCloudRunner &&
|
||||
!CloudRunner.isCloudRunnerAsyncEnvironment &&
|
||||
!CloudRunner.isCloudRunnerEnvironment
|
||||
) {
|
||||
return await AsyncWorkflow.runAsyncWorkflow(cloudRunnerStepState.environment, cloudRunnerStepState.secrets);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user