diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 35e2557b..10a5cfea 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -23,13 +23,13 @@ jobs: with: node-version: 12.x - run: yarn - - run: yarn run cli -m aws-list-tasks + - run: yarn run cli --help env: AWS_REGION: eu-west-2 AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: eu-west-2 - - run: yarn run cli -m aws-list-stacks + - run: yarn run cli -m aws-list-all env: AWS_REGION: eu-west-2 AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} diff --git a/.github/workflows/cloud-runner-aws-pipeline.yml b/.github/workflows/cloud-runner-aws-pipeline.yml deleted file mode 100644 index 832e2079..00000000 --- a/.github/workflows/cloud-runner-aws-pipeline.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: Cloud Runner - AWS Tests - -on: - push: { branches: [main, cloud-runner-develop] } - -env: - GKE_ZONE: 'us-central1' - GKE_REGION: 'us-central1' - GKE_PROJECT: 'unitykubernetesbuilder' - GKE_CLUSTER: 'unity-builder-cluster' - GCP_LOGGING: true - GCP_PROJECT: unitykubernetesbuilder - AWS_REGION: eu-west-2 - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: eu-west-2 - AWS_BASE_STACK_NAME: game-ci-github-pipelines - CLOUD_RUNNER_BRANCH: ${{ github.ref }} - CLOUD_RUNNER_TESTS: true - DEBUG: true - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} - -jobs: - buildForAllPlatforms: - name: AWS Fargate Build - if: github.event.pull_request.draft == false - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - projectPath: - - test-project - unityVersion: - # - 2019.2.11f1 - - 2019.3.15f1 - targetPlatform: - #- StandaloneOSX # Build a macOS standalone (Intel 64-bit). - - StandaloneWindows64 # Build a Windows 64-bit standalone. - - StandaloneLinux64 # Build a Linux 64-bit standalone. - - WebGL # WebGL. - #- iOS # Build an iOS player. - #- Android # Build an Android .apk. - # - StandaloneWindows # Build a Windows standalone. - # - WSAPlayer # Build an Windows Store Apps player. - # - PS4 # Build a PS4 Standalone. - # - XboxOne # Build a Xbox One Standalone. - # - tvOS # Build to Apple's tvOS platform. - # - Switch # Build a Nintendo Switch player - # steps - steps: - - name: Checkout (default) - uses: actions/checkout@v2 - if: github.event.event_type != 'pull_request_target' - with: - lfs: true - - 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 - - run: yarn - - run: yarn run cli --help - - run: yarn run test "caching" - - run: yarn run test-i-aws - env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} - PROJECT_PATH: ${{ matrix.projectPath }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TARGET_PLATFORM: ${{ matrix.targetPlatform }} - cloudRunnerTests: true - versioning: None - - uses: ./ - id: aws-fargate-unity-build - timeout-minutes: 25 - with: - cloudRunnerCluster: aws - versioning: None - projectPath: ${{ matrix.projectPath }} - unityVersion: ${{ matrix.unityVersion }} - targetPlatform: ${{ matrix.targetPlatform }} - githubToken: ${{ secrets.GITHUB_TOKEN }} - postBuildSteps: | - - name: upload - image: amazon/aws-cli - commands: | - aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID --profile default - aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY --profile default - aws configure set region $AWS_DEFAULT_REGION --profile default - aws s3 ls - aws s3 ls game-ci-test-storage - ls /data/cache/$CACHE_KEY - ls /data/cache/$CACHE_KEY/build - aws s3 cp /data/cache/$CACHE_KEY/build/build-$BUILD_GUID.tar s3://game-ci-test-storage/$CACHE_KEY/build-$BUILD_GUID.tar - secrets: - - name: awsAccessKeyId - value: ${{ secrets.AWS_ACCESS_KEY_ID }} - - name: awsSecretAccessKey - value: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - name: awsDefaultRegion - value: eu-west-2 - - run: | - aws s3 cp s3://game-ci-test-storage/${{ steps.aws-fargate-unity-build.outputs.CACHE_KEY }}/build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.tar build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.tar - ls - - run: yarn run cli -m aws-garbage-collect - ########################### - # Upload # - ########################### - # download from cloud storage - - uses: actions/upload-artifact@v2 - with: - name: AWS Build (${{ matrix.targetPlatform }}) - path: build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.tar - retention-days: 14 diff --git a/.github/workflows/cloud-runner-k8s-pipeline.yml b/.github/workflows/cloud-runner-pipeline.yml similarity index 54% rename from .github/workflows/cloud-runner-k8s-pipeline.yml rename to .github/workflows/cloud-runner-pipeline.yml index a6e8d11f..1b7c2293 100644 --- a/.github/workflows/cloud-runner-k8s-pipeline.yml +++ b/.github/workflows/cloud-runner-pipeline.yml @@ -1,7 +1,7 @@ -name: Cloud Runner - K8s Tests +name: Cloud Runner on: - push: { branches: [cloud-runner-develop] } + push: { branches: [cloud-runner-develop, main] } # push: { branches: [main] } # pull_request: # paths-ignore: @@ -26,6 +26,97 @@ env: UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} jobs: + awsBuild: + name: AWS Fargate Build + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + projectPath: + - test-project + unityVersion: + # - 2019.2.11f1 + - 2019.3.15f1 + targetPlatform: + #- StandaloneOSX # Build a macOS standalone (Intel 64-bit). + - StandaloneWindows64 # Build a Windows 64-bit standalone. + - StandaloneLinux64 # Build a Linux 64-bit standalone. + - WebGL # WebGL. + #- iOS # Build an iOS player. + #- Android # Build an Android .apk. + # - StandaloneWindows # Build a Windows standalone. + # - WSAPlayer # Build an Windows Store Apps player. + # - PS4 # Build a PS4 Standalone. + # - XboxOne # Build a Xbox One Standalone. + # - tvOS # Build to Apple's tvOS platform. + # - Switch # Build a Nintendo Switch player + # steps + steps: + - name: Checkout (default) + uses: actions/checkout@v2 + if: github.event.event_type != 'pull_request_target' + with: + lfs: true + - 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 + - run: yarn + - run: yarn run cli --help + - run: yarn run test "caching" + - run: yarn run test-i-aws + env: + UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} + PROJECT_PATH: ${{ matrix.projectPath }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TARGET_PLATFORM: ${{ matrix.targetPlatform }} + cloudRunnerTests: true + versioning: None + - uses: ./ + id: aws-fargate-unity-build + timeout-minutes: 25 + with: + cloudRunnerCluster: aws + versioning: None + projectPath: ${{ matrix.projectPath }} + unityVersion: ${{ matrix.unityVersion }} + targetPlatform: ${{ matrix.targetPlatform }} + githubToken: ${{ secrets.GITHUB_TOKEN }} + postBuildSteps: | + - name: upload + image: amazon/aws-cli + commands: | + aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID --profile default + aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY --profile default + aws configure set region $AWS_DEFAULT_REGION --profile default + aws s3 ls + aws s3 ls game-ci-test-storage + ls /data/cache/$CACHE_KEY + ls /data/cache/$CACHE_KEY/build + aws s3 cp /data/cache/$CACHE_KEY/build/build-$BUILD_GUID.tar s3://game-ci-test-storage/$CACHE_KEY/build-$BUILD_GUID.tar + secrets: + - name: awsAccessKeyId + value: ${{ secrets.AWS_ACCESS_KEY_ID }} + - name: awsSecretAccessKey + value: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: awsDefaultRegion + value: eu-west-2 + - run: | + aws s3 cp s3://game-ci-test-storage/${{ steps.aws-fargate-unity-build.outputs.CACHE_KEY }}/build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.tar build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.tar + ls + - run: yarn run cli -m aws-garbage-collect + ########################### + # Upload # + ########################### + # download from cloud storage + - uses: actions/upload-artifact@v2 + with: + name: AWS Build (${{ matrix.targetPlatform }}) + path: build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.tar + retention-days: 14 k8sBuilds: name: K8s (GKE Autopilot) build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }} runs-on: ubuntu-latest @@ -105,7 +196,6 @@ jobs: aws s3 ls aws s3 ls game-ci-test-storage ls /data/cache/$CACHE_KEY - echo "/data/cache/$CACHE_KEY/build/build-$BUILD_GUID.tar s3://game-ci-test-storage/$CACHE_KEY/$BUILD_FILE" aws s3 cp /data/cache/$CACHE_KEY/build/build-$BUILD_GUID.tar s3://game-ci-test-storage/$CACHE_KEY/build-$BUILD_GUID.tar secrets: - name: awsAccessKeyId diff --git a/dist/index.js b/dist/index.js index 476b3bcd..9494b362 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 a1617828..dbccfbe5 100644 Binary files a/dist/index.js.map and b/dist/index.js.map differ diff --git a/src/model/build-parameters.ts b/src/model/build-parameters.ts index 5f5aa185..4dcb3854 100644 --- a/src/model/build-parameters.ts +++ b/src/model/build-parameters.ts @@ -76,7 +76,7 @@ class BuildParameters { // Todo - Don't use process.env directly, that's what the input model class is for. // --- let unitySerial = ''; - if (!process.env.UNITY_SERIAL && Input.githubInputEnabled && Cli.options === undefined) { + if (!process.env.UNITY_SERIAL && Input.githubInputEnabled) { // No serial was present, so it is a personal license that we need to convert if (!process.env.UNITY_LICENSE) { throw new Error(`Missing Unity License File and no Serial was found. If this 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 8b88909b..1efb2294 100644 --- a/src/model/cloud-runner/providers/aws/aws-task-runner.ts +++ b/src/model/cloud-runner/providers/aws/aws-task-runner.ts @@ -6,8 +6,8 @@ import * as zlib from 'zlib'; import CloudRunnerLogger from '../../services/cloud-runner-logger'; import { Input } from '../../..'; import CloudRunner from '../../cloud-runner'; -import { CloudRunnerStatics } from '../../cloud-runner-statics'; import { CloudRunnerBuildCommandProcessor } from '../../services/cloud-runner-build-command-process'; +import { FollowLogStreamService } from '../../services/follow-log-stream-service'; class AWSTaskRunner { static async runTask( @@ -126,8 +126,8 @@ class AWSTaskRunner { const stream = await AWSTaskRunner.getLogStream(kinesis, kinesisStreamName); let iterator = await AWSTaskRunner.getLogIterator(kinesis, stream); - const logBaseUrl = `https://${Input.region}.console.aws.amazon.com/cloudwatch/home?region=${CF.config.region}#logsV2:log-groups/log-group/${taskDef.taskDefStackName}`; - CloudRunnerLogger.log(`You can also see the logs at AWS Cloud Watch: ${logBaseUrl}`); + const logBaseUrl = `https://${Input.region}.console.aws.amazon.com/cloudwatch/home?region=${Input.region}#logsV2:log-groups/log-group/${CloudRunner.buildParameters.awsBaseStackName}-${CloudRunner.buildParameters.buildGuid}`; + CloudRunnerLogger.log(`You view the log stream on AWS Cloud Watch: ${logBaseUrl}`); let shouldReadLogs = true; let shouldCleanup = true; let timestamp: number = 0; @@ -209,30 +209,13 @@ class AWSTaskRunner { ); if (json.messageType === 'DATA_MESSAGE') { for (let logEventsIndex = 0; logEventsIndex < json.logEvents.length; logEventsIndex++) { - let message = json.logEvents[logEventsIndex].message; - if (json.logEvents[logEventsIndex].message.includes(`---${CloudRunner.buildParameters.logId}`)) { - CloudRunnerLogger.log('End of log transmission received'); - shouldReadLogs = false; - } else if (message.includes('Rebuilding Library because the asset database could not be found!')) { - core.warning('LIBRARY NOT FOUND!'); - core.setOutput('library-found', 'false'); - } else if (message.includes('Build succeeded')) { - core.setOutput('build-result', 'success'); - } else if (message.includes('Build fail')) { - core.setOutput('build-result', 'failed'); - core.setFailed('unity build failed'); - core.error('BUILD FAILED!'); - } else if (message.includes(': Listening for Jobs')) { - core.setOutput('cloud runner stop watching', 'true'); - shouldReadLogs = false; - shouldCleanup = false; - core.warning('cloud runner stop watching'); - } - message = `[${CloudRunnerStatics.logPrefix}] ${message}`; - if (CloudRunner.buildParameters.cloudRunnerIntegrationTests) { - output += message; - } - CloudRunnerLogger.log(message); + const message = json.logEvents[logEventsIndex].message; + ({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration( + message, + shouldReadLogs, + shouldCleanup, + output, + )); } } } diff --git a/src/model/cloud-runner/providers/aws/cloud-formations/base-stack-formation.ts b/src/model/cloud-runner/providers/aws/cloud-formations/base-stack-formation.ts index 4edd4047..3cbe61d7 100644 --- a/src/model/cloud-runner/providers/aws/cloud-formations/base-stack-formation.ts +++ b/src/model/cloud-runner/providers/aws/cloud-formations/base-stack-formation.ts @@ -1,6 +1,7 @@ export class BaseStackFormation { + public static readonly baseStackDecription = `Game-CI base stack`; public static readonly formation: string = `AWSTemplateFormatVersion: '2010-09-09' -Description: Game-CI base stack +Description: ${BaseStackFormation.baseStackDecription} Parameters: EnvironmentName: Type: String diff --git a/src/model/cloud-runner/providers/aws/cloud-formations/task-definition-formation.ts b/src/model/cloud-runner/providers/aws/cloud-formations/task-definition-formation.ts index 49669c4e..78a98099 100644 --- a/src/model/cloud-runner/providers/aws/cloud-formations/task-definition-formation.ts +++ b/src/model/cloud-runner/providers/aws/cloud-formations/task-definition-formation.ts @@ -1,9 +1,7 @@ export class TaskDefinitionFormation { + public static readonly description: string = `Game CI Cloud Runner Task Stack`; public static readonly formation: string = `AWSTemplateFormatVersion: 2010-09-09 -Description: >- - AWS Fargate cluster that can span public and private subnets. Supports public - facing load balancers, private internal load balancers, and both internal and - external service discovery namespaces. +Description: ${TaskDefinitionFormation.description} Parameters: EnvironmentName: Type: String diff --git a/src/model/cloud-runner/providers/aws/commands/aws-cli-commands.ts b/src/model/cloud-runner/providers/aws/commands/aws-cli-commands.ts index deb6f9ce..7563606a 100644 --- a/src/model/cloud-runner/providers/aws/commands/aws-cli-commands.ts +++ b/src/model/cloud-runner/providers/aws/commands/aws-cli-commands.ts @@ -2,18 +2,67 @@ import AWS from 'aws-sdk'; import { CliFunction } from '../../../../cli/cli-functions-repository'; import Input from '../../../../input'; import CloudRunnerLogger from '../../../services/cloud-runner-logger'; +import { BaseStackFormation } from '../cloud-formations/base-stack-formation'; export class AwsCliCommands { + @CliFunction(`aws-list-all`, `List all resources`) + static async awsListAll() { + await AwsCliCommands.awsListStacks(undefined, true); + await AwsCliCommands.awsListTasks(); + await AwsCliCommands.awsListLogGroups(undefined, true); + } + @CliFunction(`aws-garbage-collect`, `garbage collect aws resources not in use !WIP!`) + static async garbageCollectAws() { + await AwsCliCommands.cleanup(false); + } + @CliFunction(`aws-garbage-collect-all`, `garbage collect aws resources regardless of whether they are in use`) + static async garbageCollectAwsAll() { + await AwsCliCommands.cleanup(true); + } + @CliFunction( + `aws-garbage-collect-all-1d-older`, + `garbage collect aws resources created more than 1d ago (ignore if they are in use)`, + ) + static async garbageCollectAwsAllOlderThanOneDay() { + await AwsCliCommands.cleanup(true, true); + } + static isOlderThan1day(date: any) { + const ageDate = new Date(date.getTime() - Date.now()); + + return ageDate.getDay() > 0; + } @CliFunction(`aws-list-stacks`, `List stacks`) - static async awsListStacks(perResultCallback: any) { + static async awsListStacks(perResultCallback: any = false, verbose: boolean = false) { process.env.AWS_REGION = Input.region; const CF = new AWS.CloudFormation(); const stacks = - (await CF.listStacks().promise()).StackSummaries?.filter((_x) => _x.StackStatus !== 'DELETE_COMPLETE') || []; - CloudRunnerLogger.log(`DescribeStacksRequest ${stacks.length}`); + (await CF.listStacks().promise()).StackSummaries?.filter( + (_x) => _x.StackStatus !== 'DELETE_COMPLETE', // && + // _x.TemplateDescription === TaskDefinitionFormation.description.replace('\n', ''), + ) || []; + CloudRunnerLogger.log(`Stacks ${stacks.length}`); for (const element of stacks) { - CloudRunnerLogger.log(JSON.stringify(element, undefined, 4)); - CloudRunnerLogger.log(`${element.StackName}`); + const ageDate = new Date(element.CreationTime.getTime() - Date.now()); + if (verbose) + CloudRunnerLogger.log( + `Task Stack ${element.StackName} - Age D${ageDate.getDay()} H${ageDate.getHours()} M${ageDate.getMinutes()}`, + ); + if (perResultCallback) await perResultCallback(element); + } + const baseStacks = + (await CF.listStacks().promise()).StackSummaries?.filter( + (_x) => + _x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription === BaseStackFormation.baseStackDecription, + ) || []; + CloudRunnerLogger.log(`Base Stacks ${baseStacks.length}`); + for (const element of baseStacks) { + const ageDate = new Date(element.CreationTime.getTime() - Date.now()); + if (verbose) + CloudRunnerLogger.log( + `Base Stack ${ + element.StackName + } - Age D${ageDate.getHours()} H${ageDate.getHours()} M${ageDate.getMinutes()}`, + ); if (perResultCallback) await perResultCallback(element); } if (stacks === undefined) { @@ -21,15 +70,16 @@ export class AwsCliCommands { } } @CliFunction(`aws-list-tasks`, `List tasks`) - static async awsListTasks(perResultCallback: any) { + static async awsListTasks(perResultCallback: any = false) { process.env.AWS_REGION = Input.region; - CloudRunnerLogger.log(`ECS Clusters`); const ecs = new AWS.ECS(); const clusters = (await ecs.listClusters().promise()).clusterArns || []; + CloudRunnerLogger.log(`Clusters ${clusters.length}`); for (const element of clusters) { const input: AWS.ECS.ListTasksRequest = { cluster: element, }; + const list = (await ecs.listTasks(input).promise()).taskArns || []; if (list.length > 0) { const describeInput: AWS.ECS.DescribeTasksRequest = { tasks: list, cluster: element }; @@ -37,14 +87,13 @@ export class AwsCliCommands { if (describeList === []) { continue; } - CloudRunnerLogger.log(`DescribeTasksRequest ${describeList.length}`); + CloudRunnerLogger.log(`Tasks ${describeList.length}`); for (const taskElement of describeList) { if (taskElement === undefined) { continue; } taskElement.overrides = {}; taskElement.attachments = []; - CloudRunnerLogger.log(JSON.stringify(taskElement, undefined, 4)); if (taskElement.createdAt === undefined) { CloudRunnerLogger.log(`Skipping ${taskElement.taskDefinitionArn} no createdAt date`); continue; @@ -54,39 +103,68 @@ export class AwsCliCommands { } } } + @CliFunction(`aws-list-log-groups`, `List tasks`) + static async awsListLogGroups(perResultCallback: any = false, verbose: boolean = false) { + process.env.AWS_REGION = Input.region; + const ecs = new AWS.CloudWatchLogs(); + let logStreamInput: AWS.CloudWatchLogs.DescribeLogGroupsRequest = { + /* logGroupNamePrefix: 'game-ci' */ + }; + let logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise(); + const logGroups = logGroupsDescribe.logGroups || []; + while (logGroupsDescribe.nextToken) { + logStreamInput = { /* logGroupNamePrefix: 'game-ci',*/ nextToken: logGroupsDescribe.nextToken }; + logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise(); + logGroups.push(...(logGroupsDescribe?.logGroups || [])); + } - @CliFunction(`aws-garbage-collect`, `garbage collect aws`) - static async garbageCollectAws() { - await AwsCliCommands.cleanup(false); - } - @CliFunction(`aws-garbage-collect-all`, `garbage collect aws`) - static async garbageCollectAwsAll() { - await AwsCliCommands.cleanup(true); - } - @CliFunction(`aws-garbage-collect-all-1d-older`, `garbage collect aws`) - static async garbageCollectAwsAllOlderThanOneDay() { - await AwsCliCommands.cleanup(true); + CloudRunnerLogger.log(`Log Groups ${logGroups.length}`); + for (const element of logGroups) { + if (element.creationTime === undefined) { + CloudRunnerLogger.log(`Skipping ${element.logGroupName} no createdAt date`); + continue; + } + const ageDate = new Date(new Date(element.creationTime).getTime() - Date.now()); + if (verbose) + CloudRunnerLogger.log( + `Log Group Name ${ + element.logGroupName + } - Age D${ageDate.getDay()} H${ageDate.getHours()} M${ageDate.getMinutes()} - 1d old ${AwsCliCommands.isOlderThan1day( + new Date(element.creationTime), + )}`, + ); + if (perResultCallback) await perResultCallback(element, element); + } } - private static async cleanup(deleteResources = false) { + private static async cleanup(deleteResources = false, OneDayOlderOnly: boolean = false) { process.env.AWS_REGION = Input.region; const CF = new AWS.CloudFormation(); const ecs = new AWS.ECS(); + const cwl = new AWS.CloudWatchLogs(); await AwsCliCommands.awsListStacks(async (element) => { - if (deleteResources) { + if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(element.CreationTime))) { if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') { CloudRunnerLogger.log(`Skipping ${element.StackName} ignore list`); return; } + CloudRunnerLogger.log(`Deleting ${element.logGroupName}`); const deleteStackInput: AWS.CloudFormation.DeleteStackInput = { StackName: element.StackName }; await CF.deleteStack(deleteStackInput).promise(); } }); await AwsCliCommands.awsListTasks(async (taskElement, element) => { - if (deleteResources) { + if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(taskElement.CreatedAt))) { + CloudRunnerLogger.log(`Stopping task ${taskElement.containers?.[0].name}`); await ecs.stopTask({ task: taskElement.taskArn || '', cluster: element }).promise(); } }); + await AwsCliCommands.awsListLogGroups(async (element) => { + if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(new Date(element.createdAt)))) { + CloudRunnerLogger.log(`Deleting ${element.logGroupName}`); + await cwl.deleteLogGroup({ logGroupName: element.logGroupName || '' }).promise(); + } + }); } } diff --git a/src/model/cloud-runner/providers/k8s/index.ts b/src/model/cloud-runner/providers/k8s/index.ts index 38432730..65cb4ed2 100644 --- a/src/model/cloud-runner/providers/k8s/index.ts +++ b/src/model/cloud-runner/providers/k8s/index.ts @@ -119,7 +119,6 @@ class Kubernetes implements ProviderInterface { this.podName, 'main', this.namespace, - CloudRunnerLogger.log, ); break; } catch (error: any) { 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 57cacd48..da70be19 100644 --- a/src/model/cloud-runner/providers/k8s/kubernetes-task-runner.ts +++ b/src/model/cloud-runner/providers/k8s/kubernetes-task-runner.ts @@ -4,7 +4,7 @@ import CloudRunnerLogger from '../../services/cloud-runner-logger'; import * as core from '@actions/core'; import { CloudRunnerStatics } from '../../cloud-runner-statics'; import waitUntil from 'async-wait-until'; -import CloudRunner from '../../cloud-runner'; +import { FollowLogStreamService } from '../../services/follow-log-stream-service'; class KubernetesTaskRunner { static async runTask( @@ -14,20 +14,23 @@ class KubernetesTaskRunner { podName: string, containerName: string, namespace: string, - logCallback: any, ) { CloudRunnerLogger.log(`Streaming logs from pod: ${podName} container: ${containerName} namespace: ${namespace}`); const stream = new Writable(); let output = ''; let didStreamAnyLogs: boolean = false; + let shouldReadLogs = true; + let shouldCleanup = true; stream._write = (chunk, encoding, next) => { didStreamAnyLogs = true; let message = chunk.toString().trimRight(`\n`); message = `[${CloudRunnerStatics.logPrefix}] ${message}`; - if (CloudRunner.buildParameters.cloudRunnerIntegrationTests) { - output += message; - } - logCallback(message); + ({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration( + message, + shouldReadLogs, + shouldCleanup, + output, + )); next(); }; const logOptions = { diff --git a/src/model/cloud-runner/services/follow-log-stream-service.ts b/src/model/cloud-runner/services/follow-log-stream-service.ts new file mode 100644 index 00000000..59efc9b8 --- /dev/null +++ b/src/model/cloud-runner/services/follow-log-stream-service.ts @@ -0,0 +1,34 @@ +import CloudRunnerLogger from './cloud-runner-logger'; +import * as core from '@actions/core'; +import CloudRunner from '../cloud-runner'; +import { CloudRunnerStatics } from '../cloud-runner-statics'; + +export class FollowLogStreamService { + public static handleIteration(message, shouldReadLogs, shouldCleanup, output) { + if (message.includes(`---${CloudRunner.buildParameters.logId}`)) { + CloudRunnerLogger.log('End of log transmission received'); + shouldReadLogs = false; + } else if (message.includes('Rebuilding Library because the asset database could not be found!')) { + core.warning('LIBRARY NOT FOUND!'); + core.setOutput('library-found', 'false'); + } else if (message.includes('Build succeeded')) { + core.setOutput('build-result', 'success'); + } else if (message.includes('Build fail')) { + core.setOutput('build-result', 'failed'); + core.setFailed('unity build failed'); + core.error('BUILD FAILED!'); + } else if (CloudRunner.buildParameters.cloudRunnerIntegrationTests && message.includes(': Listening for Jobs')) { + core.setOutput('cloud runner stop watching', 'true'); + shouldReadLogs = false; + shouldCleanup = false; + core.warning('cloud runner stop watching'); + } + message = `[${CloudRunnerStatics.logPrefix}] ${message}`; + if (CloudRunner.buildParameters.cloudRunnerIntegrationTests) { + output += message; + } + CloudRunnerLogger.log(message); + + return { shouldReadLogs, shouldCleanup, output }; + } +}