From f77696efae399f7a80b415c249517b3bac13a772 Mon Sep 17 00:00:00 2001 From: Frostebite Date: Thu, 5 May 2022 00:25:17 +0100 Subject: [PATCH] Cloud runner develop v0.1 (#395) * Correct aws logs link * Correct aws logs link * better aws cli commands and better cleanup for aws * better aws cli commands and better cleanup for aws * improved garbage collection cli options * Only allow ephemeral runners when using cloud runner integration tests flag to avoid unexpected hangup * Only allow ephemeral runners when using cloud runner integration tests flag to avoid unexpected hangup * fix issue #393 * Extract follow log stream service * consolidate into one pipeline file * consolidate into one pipeline file --- .github/workflows/cleanup.yml | 4 +- .../workflows/cloud-runner-aws-pipeline.yml | 114 ---------------- ...pipeline.yml => cloud-runner-pipeline.yml} | 96 +++++++++++++- dist/index.js | Bin 21853001 -> 21859062 bytes dist/index.js.map | Bin 16229307 -> 16236266 bytes src/model/build-parameters.ts | 2 +- .../providers/aws/aws-task-runner.ts | 37 ++---- .../cloud-formations/base-stack-formation.ts | 3 +- .../task-definition-formation.ts | 6 +- .../aws/commands/aws-cli-commands.ts | 124 ++++++++++++++---- src/model/cloud-runner/providers/k8s/index.ts | 1 - .../providers/k8s/kubernetes-task-runner.ts | 15 ++- .../services/follow-log-stream-service.ts | 34 +++++ 13 files changed, 254 insertions(+), 182 deletions(-) delete mode 100644 .github/workflows/cloud-runner-aws-pipeline.yml rename .github/workflows/{cloud-runner-k8s-pipeline.yml => cloud-runner-pipeline.yml} (54%) create mode 100644 src/model/cloud-runner/services/follow-log-stream-service.ts 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 476b3bcdeea6d6b34c7ab83a8476ec6fb81d7c4d..9494b362d38fa56144c80fed42135718a43af436 100644 GIT binary patch delta 5595 zcmd7Vd3Y4%9S88|-75zPB;+PBd2>j1L$XOAAU6pKryvm!2v@SnPBu$6yXnp@frbFy zBFfYV*0$f@hHOGewfgu+d7jUg%^dH% z^UgcJnR(^R{>aZZABfytVxiZ4`WUKgG=%TnaN%7#~y^o$)lN3>1U2L7=Zua}M(dTltA+P4rzkDJ_ z*H)kvziQETL>p;!sV<&8jk;)R-XanQRC)YzsZ(i@z6M32dD+pt^tksPIXAZtweQrK zXxy&jk(&!esW#_A+$y!f<9FH1U5d@^Z_bwtvmRPE8D+z%-9zR9&b~yJJwzrX`{!-iM4^fQKG%L*siEO8<%Lj+~x7x2zeQNY>@FVnK3Lw`SS`Xnsu+tv;_aY9DS&q(_ENOE!!cRx*SJ z&lUO9UMn2rR7Br?ez#qzce)jOzVvzh3Kg#w3G`*1=oe*cQKvaoA0JqyqSYcXxH#?N zVvOe`ugBl4n!B|?)pcTM;F?>(cx89aRsv8cHci~Mc(3C}vl|^(~=;G+B z`67%K&lSnBJcDd56&HW0;;r?l3Y8ty$J4=#r%kl(rDX})p^rz=Zig;go1A>f!pR=LS2bJO%`XzfR`lX<3hOi5Mdw1&1->BM4@OqGQzjnw>* zE;b^xMvGmVL>ulGhB3Wd`|oRPyLuHI+o`>cZD<(3kI~)B6Ku|*oi`imJX|pkKM~!$w1SfWW*Ui?fcxNiA-{mW} zxm>lVJH1Z#-g=WZC|FPC+I)2lTnx>YQIWFSKbPX;%H2^|wf19)*@bz$Ab6KwgL+r# zMWI+WQsz3i_|-XcWnLkjJ}c73T->#z9#jO2SuNk!%bkkLE|ogf+EJQnm97)%I+SNy zI#tdSrrgeh@gxoX?rhE)T0%SDI~q?b-)IX<3M~t5D|IOzO#&4oBe8o*=n_#&Aqfb z21U~Ea>*4dF7K4K@tvfs%l7FSFAMLrU=h7hG(DZxofp}O)9`@qxLBB!p*?%d%CL(o z==8Q_iCg?8Bb}PO($vRV7pi@9_{}g$8)}W=R9daep*8iQe^h64JYy)uCI)Gh&*6E4 z=l`T$Zu(H=@4WlFT5nP;-=Vaw3Z0we9^m{sTkhVmHauFspNfl8Tp)|>(u0Yf(^}Rt zLM?YW%RNm^cq37*J)eDFce+$+*Ck(GyHjY#i~Hi~c*#B^$wWgEmB#49Xqa7O#OBKx zZahMl2cNGq+^*%l6ru;HvZ^Xf*W#iN7KoI-mzIF8r#in2G)?95Z_ez|D9@gO)Ic#0wgexiWNi@rrhnId`tT|Ltuz0Vy!155A}wDeV1{*R+uS*Hsd+u}l1#6U)HM`X^OQkh9tFA-adx^3W995ydr?NwEJ>SarVg6|72e`G9|Hm`DVU*5~i z2WT7DZP#EWQS7qGgAILn`{diy=I}_f)8Xc2&ibAWSvp$UR44jVzeX`BknY5^?d!rB z6<`nw9Q?vdk2ys_49ZML1Dgy+{jfG7!zhkkKHr@WV)>4LEw& z_Ge0TX6-~|D!qoY(k#cbC?^aj<*7buq7`8xE}_?tY!XAK$D4bM`Nc13)Vm%lXnUnS?baK1 z=|B$#FoFrfARHnf68b`5A20~upbUU0DcZn!c*`Icp9F8gYYaIg2V6}JP$9x zFX2Ua30{U*;0U}5ufb6`2Cu_$cmsX~zlPtyZ{bb&9sC~t0B^w`;RKw7x8W4L1E=9n z@Gkrr{sL#5M2<-ulR2hvOy#&* LJd08yUV8pNosX*M delta 2779 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 - 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 }; + } +}