diff --git a/.github/workflows/cloud-runner-async-checks.yml b/.github/workflows/cloud-runner-async-checks.yml index 1cdf2a03..c902b667 100644 --- a/.github/workflows/cloud-runner-async-checks.yml +++ b/.github/workflows/cloud-runner-async-checks.yml @@ -39,20 +39,21 @@ jobs: if: github.event.event_type != 'pull_request_target' runs-on: ubuntu-latest steps: - - name: Checkout (default) - uses: actions/checkout@v2 - with: - lfs: false - - run: yarn - - run: yarn run cli -m checks-update - timeout-minutes: 180 + - timeout-minutes: 180 env: UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} PROJECT_PATH: test-project GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_PRIVATE_TOKEN: ${{ secrets.GITHUB_TOKEN }} TARGET_PLATFORM: StandaloneWindows64 cloudRunnerTests: true versioning: None CLOUD_RUNNER_CLUSTER: local-docker AWS_BASE_STACK_NAME: game-ci-github-pipelines CHECKS_UPDATE: ${{ github.event.inputs.checksObject }} + run: | + git clone -b cloud-runner-develop https://github.com/game-ci/unity-builder + cd unity-builder + yarn + ls + yarn run cli -m checks-update diff --git a/.github/workflows/cloud-runner-pipeline.yml b/.github/workflows/cloud-runner-pipeline.yml index 2e16227c..5fda40c8 100644 --- a/.github/workflows/cloud-runner-pipeline.yml +++ b/.github/workflows/cloud-runner-pipeline.yml @@ -31,6 +31,8 @@ env: UNITY_VERSION: 2019.3.15f1 USE_IL2CPP: false USE_GKE_GCLOUD_AUTH_PLUGIN: true + GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: integrationTests: @@ -49,42 +51,31 @@ jobs: uses: actions/checkout@v2 with: lfs: false - - uses: google-github-actions/auth@v1 - with: - credentials_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }} - - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@v1' - - name: Get GKE cluster credentials - run: | - export USE_GKE_GCLOUD_AUTH_PLUGIN=True - gcloud components install gke-gcloud-auth-plugin - gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $GKE_PROJECT - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-2 + - uses: google-github-actions/auth@v1 + if: matrix.CloudRunnerCluster == 'k8s' + with: + credentials_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }} + - name: 'Set up Cloud SDK' + if: matrix.CloudRunnerCluster == 'k8s' + uses: 'google-github-actions/setup-gcloud@v1' + - name: Get GKE cluster credentials + if: matrix.CloudRunnerCluster == 'k8s' + run: | + export USE_GKE_GCLOUD_AUTH_PLUGIN=True + gcloud components install gke-gcloud-auth-plugin + gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $GKE_PROJECT - run: yarn - - run: yarn run test "cloud-runner-async-workflow" --detectOpenHandles --forceExit --runInBand - if: matrix.CloudRunnerCluster != 'local-docker' - timeout-minutes: 180 - env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} - PROJECT_PATH: test-project - GIT_PRIVATE_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TARGET_PLATFORM: StandaloneWindows64 - cloudRunnerTests: true - versioning: None - CLOUD_RUNNER_CLUSTER: ${{ matrix.cloudRunnerCluster }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: yarn run test-i --detectOpenHandles --forceExit --runInBand - if: matrix.CloudRunnerCluster == 'local-docker' timeout-minutes: 180 env: UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} PROJECT_PATH: test-project - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TARGET_PLATFORM: StandaloneWindows64 cloudRunnerTests: true versioning: None @@ -114,14 +105,12 @@ jobs: - run: yarn - uses: ./ id: unity-build - timeout-minutes: 90 + timeout-minutes: 30 env: UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} with: cloudRunnerTests: true versioning: None - projectPath: test-project - gitPrivateToken: ${{ secrets.GITHUB_TOKEN }} targetPlatform: ${{ matrix.targetPlatform }} cloudRunnerCluster: ${{ matrix.cloudRunnerCluster }} - run: | diff --git a/dist/exec-child.js b/dist/exec-child.js index eab86ed3..3a370bae 100644 Binary files a/dist/exec-child.js and b/dist/exec-child.js differ diff --git a/dist/index.js b/dist/index.js index 7ae33ee0..4a4d19c3 100644 Binary files a/dist/index.js and b/dist/index.js differ diff --git a/dist/index.js.map b/dist/index.js.map index a5fdf9eb..84ec0fe0 100644 Binary files a/dist/index.js.map and b/dist/index.js.map differ diff --git a/dist/licenses.txt b/dist/licenses.txt index 98b21314..08285b6c 100644 Binary files a/dist/licenses.txt and b/dist/licenses.txt differ diff --git a/dist/sourcemap-register.js b/dist/sourcemap-register.js index b9d830e9..bfdce2e7 100644 Binary files a/dist/sourcemap-register.js and b/dist/sourcemap-register.js differ diff --git a/dist/xhr-sync-worker.js b/dist/xhr-sync-worker.js index f6389cfd..cd3d0232 100644 Binary files a/dist/xhr-sync-worker.js and b/dist/xhr-sync-worker.js differ diff --git a/src/model/build-parameters.ts b/src/model/build-parameters.ts index 581036d1..47ab13dd 100644 --- a/src/model/build-parameters.ts +++ b/src/model/build-parameters.ts @@ -71,6 +71,9 @@ class BuildParameters { public garbageCollectionMaxAge!: number; public constantGarbageCollection!: boolean; public githubChecks!: boolean; + public asyncWorkflow!: boolean; + public githubCheckId!: string; + public triggerWorkflowOnComplete!: string[]; static async create(): Promise { const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform, Input.androidAppBundle); @@ -155,6 +158,9 @@ class BuildParameters { constantGarbageCollection: CloudRunnerOptions.constantGarbageCollection, garbageCollectionMaxAge: CloudRunnerOptions.garbageCollectionMaxAge, githubChecks: CloudRunnerOptions.githubChecks, + asyncWorkflow: CloudRunnerOptions.asyncCloudRunner, + githubCheckId: CloudRunnerOptions.githubCheckId, + triggerWorkflowOnComplete: CloudRunnerOptions.triggerWorkflowOnComplete, }; } diff --git a/src/model/cli/cli.ts b/src/model/cli/cli.ts index 687c5451..9ee4854c 100644 --- a/src/model/cli/cli.ts +++ b/src/model/cli/cli.ts @@ -56,7 +56,7 @@ export class Cli { program.parse(process.argv); Cli.options = program.opts(); - return Cli.isCliMode; + return Cli.isCliMode || process.env.GAMECI_CLI; } static async RunCli(): Promise { @@ -113,12 +113,16 @@ export class Cli { public static async asyncronousWorkflow(): Promise { const buildParameter = await BuildParameters.create(); const baseImage = new ImageTag(buildParameter); + await CloudRunner.setup(buildParameter); return await CloudRunner.run(buildParameter, baseImage.toString()); } @CliFunction(`checks-update`, `runs a cloud runner build`) public static async checksUpdate() { + const buildParameter = await BuildParameters.create(); + + await CloudRunner.setup(buildParameter); const input = JSON.parse(process.env.CHECKS_UPDATE || ``); core.info(`Checks Update ${process.env.CHECKS_UPDATE}`); if (input.mode === `create`) { diff --git a/src/model/cloud-runner/cloud-runner-options.ts b/src/model/cloud-runner/cloud-runner-options.ts index 023e4648..6f3d8137 100644 --- a/src/model/cloud-runner/cloud-runner-options.ts +++ b/src/model/cloud-runner/cloud-runner-options.ts @@ -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 { diff --git a/src/model/cloud-runner/cloud-runner.ts b/src/model/cloud-runner/cloud-runner.ts index d1f716a3..f188329a 100644 --- a/src/model/cloud-runner/cloud-runner.ts +++ b/src/model/cloud-runner/cloud-runner.ts @@ -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); } diff --git a/src/model/cloud-runner/providers/aws/aws-task-runner.ts b/src/model/cloud-runner/providers/aws/aws-task-runner.ts index 6d5bd8e3..5e0277b0 100644 --- a/src/model/cloud-runner/providers/aws/aws-task-runner.ts +++ b/src/model/cloud-runner/providers/aws/aws-task-runner.ts @@ -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`); diff --git a/src/model/cloud-runner/providers/k8s/index.ts b/src/model/cloud-runner/providers/k8s/index.ts index c06d1682..9cc69778 100644 --- a/src/model/cloud-runner/providers/k8s/index.ts +++ b/src/model/cloud-runner/providers/k8s/index.ts @@ -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; diff --git a/src/model/cloud-runner/providers/k8s/kubernetes-task-runner.ts b/src/model/cloud-runner/providers/k8s/kubernetes-task-runner.ts index da70be19..95dcfc41 100644 --- a/src/model/cloud-runner/providers/k8s/kubernetes-task-runner.ts +++ b/src/model/cloud-runner/providers/k8s/kubernetes-task-runner.ts @@ -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'); diff --git a/src/model/cloud-runner/remote-client/index.ts b/src/model/cloud-runner/remote-client/index.ts index 9bd3533c..b80dbdeb 100644 --- a/src/model/cloud-runner/remote-client/index.ts +++ b/src/model/cloud-runner/remote-client/index.ts @@ -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}`); } } diff --git a/src/model/cloud-runner/services/follow-log-stream-service.ts b/src/model/cloud-runner/services/follow-log-stream-service.ts index 486fb356..fcc88915 100644 --- a/src/model/cloud-runner/services/follow-log-stream-service.ts +++ b/src/model/cloud-runner/services/follow-log-stream-service.ts @@ -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; diff --git a/src/model/cloud-runner/workflows/async-workflow.ts b/src/model/cloud-runner/workflows/async-workflow.ts index ffac8478..05d0d0d4 100644 --- a/src/model/cloud-runner/workflows/async-workflow.ts +++ b/src/model/cloud-runner/workflows/async-workflow.ts @@ -11,6 +11,9 @@ export class AsyncWorkflow { ): Promise { 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, ...[ diff --git a/src/model/cloud-runner/workflows/workflow-composition-root.ts b/src/model/cloud-runner/workflows/workflow-composition-root.ts index d13cc06a..39010875 100644 --- a/src/model/cloud-runner/workflows/workflow-composition-root.ts +++ b/src/model/cloud-runner/workflows/workflow-composition-root.ts @@ -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); } diff --git a/src/model/github.ts b/src/model/github.ts index 75255f10..8d41c2c7 100644 --- a/src/model/github.ts +++ b/src/model/github.ts @@ -9,6 +9,7 @@ class GitHub { private static longDescriptionContent: string = ``; private static startedDate: string; private static endedDate: string; + static result: string = ``; private static get octokitDefaultToken() { return new Octokit({ auth: process.env.GITHUB_TOKEN, @@ -32,7 +33,7 @@ class GitHub { } private static get checkRunId() { - return CloudRunner.githubCheckId; + return CloudRunner.buildParameters.githubCheckId; } private static get owner() { @@ -44,13 +45,12 @@ class GitHub { } public static async createGitHubCheck(summary) { - if (!CloudRunnerOptions.githubChecks) { + if (!CloudRunnerOptions.githubChecks || CloudRunner.isCloudRunnerEnvironment) { return ``; } GitHub.startedDate = new Date().toISOString(); - CloudRunnerLogger.log(`POST /repos/${GitHub.owner}/${GitHub.repo}/check-runs`); - + CloudRunnerLogger.log(`Creating inital github check`); const data = { owner: GitHub.owner, repo: GitHub.repo, @@ -77,15 +77,20 @@ class GitHub { }; const result = await GitHub.createGitHubCheckRequest(data); - return result.data.id; + return result.data.id.toString(); } public static async updateGitHubCheck(longDescription, summary, result = `neutral`, status = `in_progress`) { - if (!CloudRunnerOptions.githubChecks) { + const isLocalAsync = CloudRunner.buildParameters.asyncWorkflow && !CloudRunner.isCloudRunnerAsyncEnvironment; + if (!CloudRunnerOptions.githubChecks || isLocalAsync) { return; } GitHub.longDescriptionContent += `\n${longDescription}`; - + if (GitHub.result !== `success` && GitHub.result !== `failure`) { + GitHub.result = result; + } else { + result = GitHub.result; + } const data: any = { owner: GitHub.owner, repo: GitHub.repo, @@ -114,11 +119,13 @@ class GitHub { data.conclusion = result; } - if (await CloudRunnerOptions.asyncCloudRunner) { + if (CloudRunner.isCloudRunnerAsyncEnvironment) { + CloudRunnerLogger.log(`Updating check via async update workflow`); await GitHub.runUpdateAsyncChecksWorkflow(data, `update`); return; } + CloudRunnerLogger.log(`Updating check via direct call`); await GitHub.updateGitHubCheckRequest(data); } @@ -134,18 +141,16 @@ class GitHub { if (mode === `create`) { throw new Error(`Not supported: only use update`); } - const workflowsResult = await GitHub.octokitDefaultToken.request( - `GET /repos/${GitHub.owner}/${GitHub.repo}/actions/workflows`, - { - owner: GitHub.owner, - repo: GitHub.repo, - }, - ); + const workflowsResult = await GitHub.octokitPAT.request(`GET /repos/{owner}/{repo}/actions/workflows`, { + owner: GitHub.owner, + repo: GitHub.repo, + }); const workflows = workflowsResult.data.workflows; + CloudRunnerLogger.log(`Got ${workflows.length} workflows`); let selectedId = ``; for (let index = 0; index < workflowsResult.data.total_count; index++) { if (workflows[index].name === GitHub.asyncChecksApiWorkflowName) { - selectedId = workflows[index].id; + selectedId = workflows[index].id.toString(); } } if (selectedId === ``) { @@ -163,6 +168,37 @@ class GitHub { }, }); } + + static async triggerWorkflowOnComplete(triggerWorkflowOnComplete: string[]) { + const workflowsResult = await GitHub.octokitPAT.request(`GET /repos/{owner}/{repo}/actions/workflows`, { + owner: GitHub.owner, + repo: GitHub.repo, + }); + const workflows = workflowsResult.data.workflows; + CloudRunnerLogger.log(`Got ${workflows.length} workflows`); + for (const element of triggerWorkflowOnComplete) { + let selectedId = ``; + for (let index = 0; index < workflowsResult.data.total_count; index++) { + if (workflows[index].name === element) { + selectedId = workflows[index].id.toString(); + } + } + if (selectedId === ``) { + core.info(JSON.stringify(workflows)); + throw new Error(`no workflow with name "${GitHub.asyncChecksApiWorkflowName}"`); + } + await GitHub.octokitPAT.request(`POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches`, { + owner: GitHub.owner, + repo: GitHub.repo, + // eslint-disable-next-line camelcase + workflow_id: selectedId, + ref: CloudRunnerOptions.branch, + inputs: { + buildGuid: CloudRunner.buildParameters.buildGuid, + }, + }); + } + } } export default GitHub; diff --git a/src/model/input.ts b/src/model/input.ts index 4a0993ae..a27894f3 100644 --- a/src/model/input.ts +++ b/src/model/input.ts @@ -64,8 +64,8 @@ class Input { static get gitSha() { if (Input.getInput(`GITHUB_SHA`)) { return Input.getInput(`GITHUB_SHA`); - } else if (Input.getInput(`GitSHA`)) { - return Input.getInput(`GitSHA`); + } else if (Input.getInput(`GitSha`)) { + return Input.getInput(`GitSha`); } } @@ -74,7 +74,7 @@ class Input { } static get runNumber() { - return Input.getInput('GITHUB_RUN_NUMBER') || '0'; + return Input.getInput('GITHUB_RUN_NUMBER') || Input.getInput('runNumber') || '0'; } static get targetPlatform() { @@ -168,7 +168,7 @@ class Input { } static get gitPrivateToken() { - return core.getInput('gitPrivateToken') || false; + return Input.getInput('gitPrivateToken') || false; } static get chownFilesTo() {