From 8abce48a488351f8b4d3118ca7e4baaf22fc2041 Mon Sep 17 00:00:00 2001 From: Frostebite Date: Fri, 22 Apr 2022 00:47:45 +0100 Subject: [PATCH] Cloud runner v0.2 - continued quality of life improvements (#387) * Update cloud-runner-aws-pipeline.yml * Update cloud-runner-k8s-pipeline.yml * yarn build * yarn build * correct branch ref * correct branch ref passed to target repo * Create k8s-tests.yml * Delete k8s-tests.yml * correct branch ref passed to target repo * correct branch ref passed to target repo * Always describe AWS tasks for now, because unstable error handling * Remove unused tree commands * Use lfs guid sum * Simple override cache push * Simple override cache push and pull override to allow pure cloud storage driven caching * Removal of early branch (breaks lfs caching) * Remove unused tree commands * Update action.yml * Update action.yml * Support cache and input override commands as input + full support custom hooks * Increase k8s timeout * replace filename being appended for unknclear reason * cache key should not contain whitespaces * Always try and deploy rook for k8s * Apply k8s files for rook * Update action.yml * Apply k8s files for rook * Apply k8s files for rook * cache test and action description for kuber storage class * Correct test and implement dependency health check and start * GCP-secret run, cache key * lfs smudge set explicit and undo explicit * Run using external secret provider to speed up input * Update cloud-runner-aws-pipeline.yml * Add nodejs as build step dependency * Add nodejs as build step dependency * Cloud Runner Tests must be specified to capture logs from cloud runner for tests * Cloud Runner Tests must be specified to capture logs from cloud runner for tests * Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs * Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs * Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs * Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs * Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs * better defaults for new inputs * better defaults * merge latest * force build update * use npm n to update node in unity builder * use npm n to update node in unity builder * use npm n to update node in unity builder * correct new line * quiet zipping * quiet zipping * default secrets for unity username and password * default secrets for unity username and password * ls active directory before lfs install * Get cloud runner secrets from * Get cloud runner secrets from * Cleanup setup of default secrets * Various fixes * Cleanup setup of default secrets * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * Various fixes * AWS secrets manager support * less caching logs * default k8s storage class to pd-standard * more readable build commands * Capture aws exit code 1 reliably * Always replace /head from branch * k8s default storage class to standard-rwo * cleanup * further cleanup input * further cleanup input * further cleanup input * further cleanup input * further cleanup input * folder sizes to inspect caching * dir command for local cloud runner test * k8s wait for pending because pvc will not create earlier * prefer k8s standard storage * handle empty string as cloud runner cluster input * local-system is now used for cloud runner test implementation AND correctly unset test CLI input * local-system is now used for cloud runner test implementation AND correctly unset test CLI input * fix unterminated quote * fix unterminated quote * do not share build parameters in tests - in cloud runner this will cause conflicts with resouces of the same name * remove head and heads from branch prefix * fix reversed caching direction of cache-push * fixes * fixes * fixes * cachePull cli * fixes * fixes * fixes * fixes * fixes * order cache test to be first * order cache test to be first * fixes * populate cache key instead of using branch * cleanup cli * garbage-collect-aws cli can iterate over aws resources and cli scans all ts files * import cli methods * import cli files explicitly * import cli files explicitly * import cli files explicitly * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * import cli methods * log parameters in cloud runner parameter test * log parameters in cloud runner parameter test * log parameters in cloud runner parameter test * Cloud runner param test before caching because we have a fast local cache test now * Using custom build path relative to repo root rather than project root * aws-garbage-collect at end of pipeline * aws-garbage-collect do not actually delete anything for now - just list * remove some legacy du commands * Update cloud-runner-aws-pipeline.yml * log contents after cache pull and fix some scenarios with duplicate secrets * log contents after cache pull and fix some scenarios with duplicate secrets * log contents after cache pull and fix some scenarios with duplicate secrets * PR comments * Replace guid with uuid package * use fileExists lambda instead of stat to check file exists in caching * build failed results in core error message * Delete sample.txt * cloud-runner-system prefix changed to cloud-runner * Update cloud-runner-aws-pipeline.yml * remove du from caching, should run manually if interested in size, adds too much runtime to job to include by default * github ephemeral pipeline support * github ephemeral pipeline support * Merge remote-tracking branch 'origin/main' into cloud-runner-develop # Conflicts: # dist/index.js.map # src/model/cloud-runner/providers/aws/aws-task-runner.ts # src/model/cloud-runner/providers/aws/index.ts * garbage collection * garbage collection * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * self hosted runner pipeline * ephemeral runner pipeline * ephemeral runner pipeline * ephemeral runner pipeline * download runner each time * download runner each time * download runner each time * garbage collect all older than 1d as part of cleanup * download runner each time * number container cpu and memory for aws * per provider container defaults * per provider container defaults * per provider container defaults * per provider container defaults * Skip printing size unless cloudRunnerIntegrationTests is true * transition zip usage in cache to uncompressed tar for speed * transition zip usage in cache to uncompressed tar for speed * transition zip usage in cache to uncompressed tar for speed * transition zip usage in cache to uncompressed tar for speed * per provider container defaults * per provider container defaults * per provider container defaults * per provider container defaults * per provider container defaults * per provider container defaults * per provider container defaults * per provider container defaults * baked in cloud formation template * baked in cloud formation template * baked in cloud formation template * baked in cloud formation template * baked in cloud formation template * baked in cloud formation template * baked in cloud formation template * baked in cloud formation template * better aws commands * better aws commands * parse number for cloud formation template * remove container resource defaults from actions yaml * remove container resource defaults from actions yaml * skip all input readers when cloud runner is local * prefer fs/promises * actually set aws cloud runner step as failure if unity build fails * default to 3gb of ram - webgl fails on 2 --- .github/workflows/cleanup.yml | 23 + .../workflows/cloud-runner-aws-pipeline.yml | 8 +- .../workflows/cloud-runner-k8s-pipeline.yml | 8 +- action.yml | 4 +- dist/cloud-formations/base-setup.yml | 416 ------------------ dist/cloud-formations/task-def-formation.yml | 221 ---------- dist/index.js | Bin 21829741 -> 21853001 bytes dist/index.js.map | Bin 16201635 -> 16229307 bytes .../cloud-runner/cloud-runner-statics.ts | 2 +- .../providers/aws/aws-base-stack.ts | 5 +- .../aws/aws-cloud-formation-templates.ts | 4 +- .../providers/aws/aws-job-stack.ts | 32 +- .../providers/aws/aws-task-runner.ts | 39 +- ...base-setup.yml => base-stack-formation.ts} | 32 +- ...ation.yml => task-definition-formation.ts} | 81 +--- .../aws/commands/aws-cli-commands.ts | 76 +++- src/model/cloud-runner/providers/aws/index.ts | 15 +- .../k8s/kubernetes-job-spec-factory.ts | 4 +- .../cloud-runner/remote-client/caching.ts | 27 +- src/model/cloud-runner/remote-client/index.ts | 6 +- .../workflows/build-automation-workflow.ts | 2 +- .../input-readers/generic-input-reader.ts | 5 + src/model/input-readers/git-repo.ts | 7 + src/model/input-readers/github-cli.ts | 4 + .../input-readers/test-license-reader.ts | 4 + src/model/input.ts | 4 +- 26 files changed, 240 insertions(+), 789 deletions(-) delete mode 100644 dist/cloud-formations/base-setup.yml delete mode 100644 dist/cloud-formations/task-def-formation.yml rename src/model/cloud-runner/providers/aws/cloud-formations/{base-setup.yml => base-stack-formation.ts} (93%) rename src/model/cloud-runner/providers/aws/cloud-formations/{task-def-formation.yml => task-definition-formation.ts} (69%) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 844eca1d..35e2557b 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -12,3 +12,26 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} expire-in: 21 days + cleanupCloudRunner: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + if: github.event.event_type != 'pull_request_target' + with: + lfs: true + - uses: actions/setup-node@v2 + with: + node-version: 12.x + - run: yarn + - run: yarn run cli -m aws-list-tasks + 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 + 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 diff --git a/.github/workflows/cloud-runner-aws-pipeline.yml b/.github/workflows/cloud-runner-aws-pipeline.yml index 37e0df24..832e2079 100644 --- a/.github/workflows/cloud-runner-aws-pipeline.yml +++ b/.github/workflows/cloud-runner-aws-pipeline.yml @@ -37,9 +37,9 @@ jobs: #- 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. - #- WebGL # WebGL. # - StandaloneWindows # Build a Windows standalone. # - WSAPlayer # Build an Windows Store Apps player. # - PS4 # Build a PS4 Standalone. @@ -91,7 +91,7 @@ jobs: 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.zip s3://game-ci-test-storage/$CACHE_KEY/build-$BUILD_GUID.zip + 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 }} @@ -100,7 +100,7 @@ jobs: - 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 }}.zip build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.zip + 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 ########################### @@ -110,5 +110,5 @@ jobs: - uses: actions/upload-artifact@v2 with: name: AWS Build (${{ matrix.targetPlatform }}) - path: build-${{ steps.aws-fargate-unity-build.outputs.BUILD_GUID }}.zip + 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-k8s-pipeline.yml index 1b5b1509..a6e8d11f 100644 --- a/.github/workflows/cloud-runner-k8s-pipeline.yml +++ b/.github/workflows/cloud-runner-k8s-pipeline.yml @@ -105,8 +105,8 @@ 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.zip s3://game-ci-test-storage/$CACHE_KEY/$BUILD_FILE" - aws s3 cp /data/cache/$CACHE_KEY/build/build-$BUILD_GUID.zip s3://game-ci-test-storage/$CACHE_KEY/build-$BUILD_GUID.zip + 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 value: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -115,7 +115,7 @@ jobs: - name: awsDefaultRegion value: eu-west-2 - run: | - aws s3 cp s3://game-ci-test-storage/${{ steps.k8s-unity-build.outputs.CACHE_KEY }}/build-${{ steps.k8s-unity-build.outputs.BUILD_GUID }}.zip build-${{ steps.k8s-unity-build.outputs.BUILD_GUID }}.zip + aws s3 cp s3://game-ci-test-storage/${{ steps.k8s-unity-build.outputs.CACHE_KEY }}/build-${{ steps.k8s-unity-build.outputs.BUILD_GUID }}.tar build-${{ steps.k8s-unity-build.outputs.BUILD_GUID }}.tar ls ########################### # Upload # @@ -124,5 +124,5 @@ jobs: - uses: actions/upload-artifact@v2 with: name: K8s Build (${{ matrix.targetPlatform }}) - path: build-${{ steps.k8s-unity-build.outputs.BUILD_GUID }}.zip + path: build-${{ steps.k8s-unity-build.outputs.BUILD_GUID }}.tar retention-days: 14 diff --git a/action.yml b/action.yml index 953bd58b..1e0e6a95 100644 --- a/action.yml +++ b/action.yml @@ -115,11 +115,11 @@ inputs: required: false description: 'Either local, k8s or aws can be used to run builds on a remote cluster. Additional parameters must be configured.' cloudRunnerCpu: - default: '1.0' + default: '' required: false description: 'Amount of CPU time to assign the remote build container' cloudRunnerMemory: - default: '750M' + default: '' required: false description: 'Amount of memory to assign the remote build container' cachePushOverrideCommand: diff --git a/dist/cloud-formations/base-setup.yml b/dist/cloud-formations/base-setup.yml deleted file mode 100644 index d5cb05cd..00000000 --- a/dist/cloud-formations/base-setup.yml +++ /dev/null @@ -1,416 +0,0 @@ -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. -Parameters: - EnvironmentName: - Type: String - Default: development - Description: "Your deployment environment: DEV, QA , PROD" - Version: - Type: String - Description: "hash of template" - - # ContainerPort: - # Type: Number - # Default: 80 - # Description: What port number the application inside the docker container is binding to - - - -Mappings: - # Hard values for the subnet masks. These masks define - # the range of internal IP addresses that can be assigned. - # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 - # There are four subnets which cover the ranges: - # - # 10.0.0.0 - 10.0.0.255 - # 10.0.1.0 - 10.0.1.255 - # 10.0.2.0 - 10.0.2.255 - # 10.0.3.0 - 10.0.3.255 - - SubnetConfig: - VPC: - CIDR: '10.0.0.0/16' - PublicOne: - CIDR: '10.0.0.0/24' - PublicTwo: - CIDR: '10.0.1.0/24' - -Resources: - - - - # VPC in which containers will be networked. - # It has two public subnets, and two private subnets. - # We distribute the subnets across the first two available subnets - # for the region, for high availability. - VPC: - Type: AWS::EC2::VPC - Properties: - EnableDnsSupport: true - EnableDnsHostnames: true - CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] - - EFSServerSecurityGroup: - Type: AWS::EC2::SecurityGroup - Properties: - GroupName: "efs-server-endpoints" - GroupDescription: Which client ip addrs are allowed to access EFS server - VpcId: !Ref 'VPC' - SecurityGroupIngress: - - IpProtocol: tcp - FromPort: 2049 - ToPort: 2049 - SourceSecurityGroupId: !Ref ContainerSecurityGroup - #CidrIp: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] - # A security group for the containers we will run in Fargate. - # Rules are added to this security group based on what ingress you - # add for the cluster. - ContainerSecurityGroup: - Type: AWS::EC2::SecurityGroup - Properties: - GroupName: "task security group" - GroupDescription: Access to the Fargate containers - VpcId: !Ref 'VPC' - # SecurityGroupIngress: - # - IpProtocol: tcp - # FromPort: !Ref ContainerPort - # ToPort: !Ref ContainerPort - # CidrIp: 0.0.0.0/0 - SecurityGroupEgress: - - IpProtocol: -1 - FromPort: 2049 - ToPort: 2049 - CidrIp: "0.0.0.0/0" - - - - - # Two public subnets, where containers can have public IP addresses - PublicSubnetOne: - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select - - 0 - - Fn::GetAZs: !Ref 'AWS::Region' - VpcId: !Ref 'VPC' - CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] - # MapPublicIpOnLaunch: true - - PublicSubnetTwo: - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select - - 1 - - Fn::GetAZs: !Ref 'AWS::Region' - VpcId: !Ref 'VPC' - CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] - # MapPublicIpOnLaunch: true - - - # Setup networking resources for the public subnets. Containers - # in the public subnets have public IP addresses and the routing table - # sends network traffic via the internet gateway. - InternetGateway: - Type: AWS::EC2::InternetGateway - GatewayAttachement: - Type: AWS::EC2::VPCGatewayAttachment - Properties: - VpcId: !Ref 'VPC' - InternetGatewayId: !Ref 'InternetGateway' - - # Attaching a Internet Gateway to route table makes it public. - PublicRouteTable: - Type: AWS::EC2::RouteTable - Properties: - VpcId: !Ref 'VPC' - PublicRoute: - Type: AWS::EC2::Route - DependsOn: GatewayAttachement - Properties: - RouteTableId: !Ref 'PublicRouteTable' - DestinationCidrBlock: '0.0.0.0/0' - GatewayId: !Ref 'InternetGateway' - - # Attaching a public route table makes a subnet public. - PublicSubnetOneRouteTableAssociation: - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnetOne - RouteTableId: !Ref PublicRouteTable - PublicSubnetTwoRouteTableAssociation: - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnetTwo - RouteTableId: !Ref PublicRouteTable - - - - # ECS Resources - ECSCluster: - Type: AWS::ECS::Cluster - - - - # A role used to allow AWS Autoscaling to inspect stats and adjust scaleable targets - # on your AWS account - AutoscalingRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Statement: - - Effect: Allow - Principal: - Service: [application-autoscaling.amazonaws.com] - Action: ['sts:AssumeRole'] - Path: / - Policies: - - PolicyName: service-autoscaling - PolicyDocument: - Statement: - - Effect: Allow - Action: - - 'application-autoscaling:*' - - 'cloudwatch:DescribeAlarms' - - 'cloudwatch:PutMetricAlarm' - - 'ecs:DescribeServices' - - 'ecs:UpdateService' - Resource: '*' - - # This is an IAM role which authorizes ECS to manage resources on your - # account on your behalf, such as updating your load balancer with the - # details of where your containers are, so that traffic can reach your - # containers. - ECSRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Statement: - - Effect: Allow - Principal: - Service: [ecs.amazonaws.com] - Action: ['sts:AssumeRole'] - Path: / - Policies: - - PolicyName: ecs-service - PolicyDocument: - Statement: - - Effect: Allow - Action: - # Rules which allow ECS to attach network interfaces to instances - # on your behalf in order for awsvpc networking mode to work right - - 'ec2:AttachNetworkInterface' - - 'ec2:CreateNetworkInterface' - - 'ec2:CreateNetworkInterfacePermission' - - 'ec2:DeleteNetworkInterface' - - 'ec2:DeleteNetworkInterfacePermission' - - 'ec2:Describe*' - - 'ec2:DetachNetworkInterface' - - # Rules which allow ECS to update load balancers on your behalf - # with the information sabout how to send traffic to your containers - - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' - - 'elasticloadbalancing:DeregisterTargets' - - 'elasticloadbalancing:Describe*' - - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' - - 'elasticloadbalancing:RegisterTargets' - Resource: '*' - - # This is a role which is used by the ECS tasks themselves. - ECSTaskExecutionRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Statement: - - Effect: Allow - Principal: - Service: [ecs-tasks.amazonaws.com] - Action: ['sts:AssumeRole'] - Path: / - Policies: - - PolicyName: AmazonECSTaskExecutionRolePolicy - PolicyDocument: - Statement: - - Effect: Allow - Action: - - # Allow the use of secret manager - - 'secretsmanager:GetSecretValue' - - 'kms:Decrypt' - - # Allow the ECS Tasks to download images from ECR - - 'ecr:GetAuthorizationToken' - - 'ecr:BatchCheckLayerAvailability' - - 'ecr:GetDownloadUrlForLayer' - - 'ecr:BatchGetImage' - - # Allow the ECS tasks to upload logs to CloudWatch - - 'logs:CreateLogStream' - - 'logs:PutLogEvents' - Resource: '*' - - DeleteCFNLambdaExecutionRole: - Type: "AWS::IAM::Role" - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Principal: - Service: ["lambda.amazonaws.com"] - Action: "sts:AssumeRole" - Path: "/" - Policies: - - PolicyName: DeleteCFNLambdaExecutionRole - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Action: - - "logs:CreateLogGroup" - - "logs:CreateLogStream" - - "logs:PutLogEvents" - Resource: "arn:aws:logs:*:*:*" - - Effect: "Allow" - Action: - - "cloudformation:DeleteStack" - - "kinesis:DeleteStream" - - "secretsmanager:DeleteSecret" - - "kinesis:DescribeStreamSummary" - - "logs:DeleteLogGroup" - - "logs:DeleteSubscriptionFilter" - - "ecs:DeregisterTaskDefinition" - - "lambda:DeleteFunction" - - "lambda:InvokeFunction" - - "events:RemoveTargets" - - "events:DeleteRule" - - "lambda:RemovePermission" - Resource: "*" - -### cloud watch to kinesis role - - CloudWatchIAMRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Statement: - - Effect: Allow - Principal: - Service: [logs.amazonaws.com] - Action: ['sts:AssumeRole'] - Path: / - Policies: - - PolicyName: service-autoscaling - PolicyDocument: - Statement: - - Effect: Allow - Action: - - 'kinesis:PutRecord' - Resource: '*' -#####################EFS##################### - - EfsFileStorage: - Type: 'AWS::EFS::FileSystem' - Properties: - BackupPolicy: - Status: ENABLED - PerformanceMode: maxIO - Encrypted: false - - - FileSystemPolicy: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Action: - - "elasticfilesystem:ClientMount" - - "elasticfilesystem:ClientWrite" - - "elasticfilesystem:ClientRootAccess" - Principal: - AWS: "*" - - - MountTargetResource1: - Type: AWS::EFS::MountTarget - Properties: - FileSystemId: !Ref EfsFileStorage - SubnetId: !Ref PublicSubnetOne - SecurityGroups: - - !Ref EFSServerSecurityGroup - - MountTargetResource2: - Type: AWS::EFS::MountTarget - Properties: - FileSystemId: !Ref EfsFileStorage - SubnetId: !Ref PublicSubnetTwo - SecurityGroups: - - !Ref EFSServerSecurityGroup - - - - - - - - -Outputs: - - EfsFileStorageId: - Description: 'The connection endpoint for the database.' - Value: !Ref EfsFileStorage - Export: - Name: !Sub ${EnvironmentName}:EfsFileStorageId - ClusterName: - Description: The name of the ECS cluster - Value: !Ref 'ECSCluster' - Export: - Name: !Sub ${EnvironmentName}:ClusterName - AutoscalingRole: - Description: The ARN of the role used for autoscaling - Value: !GetAtt 'AutoscalingRole.Arn' - Export: - Name: !Sub ${EnvironmentName}:AutoscalingRole - ECSRole: - Description: The ARN of the ECS role - Value: !GetAtt 'ECSRole.Arn' - Export: - Name: !Sub ${EnvironmentName}:ECSRole - ECSTaskExecutionRole: - Description: The ARN of the ECS role tsk execution role - Value: !GetAtt 'ECSTaskExecutionRole.Arn' - Export: - Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole - - DeleteCFNLambdaExecutionRole: - Description: Lambda execution role for cleaning up cloud formations - Value: !GetAtt 'DeleteCFNLambdaExecutionRole.Arn' - Export: - Name: !Sub ${EnvironmentName}:DeleteCFNLambdaExecutionRole - - CloudWatchIAMRole: - Description: The ARN of the CloudWatch role for subscription filter - Value: !GetAtt 'CloudWatchIAMRole.Arn' - Export: - Name: !Sub ${EnvironmentName}:CloudWatchIAMRole - VpcId: - Description: The ID of the VPC that this stack is deployed in - Value: !Ref 'VPC' - Export: - Name: !Sub ${EnvironmentName}:VpcId - PublicSubnetOne: - Description: Public subnet one - Value: !Ref 'PublicSubnetOne' - Export: - Name: !Sub ${EnvironmentName}:PublicSubnetOne - PublicSubnetTwo: - Description: Public subnet two - Value: !Ref 'PublicSubnetTwo' - Export: - Name: !Sub ${EnvironmentName}:PublicSubnetTwo - - ContainerSecurityGroup: - Description: A security group used to allow Fargate containers to receive traffic - Value: !Ref 'ContainerSecurityGroup' - Export: - Name: !Sub ${EnvironmentName}:ContainerSecurityGroup diff --git a/dist/cloud-formations/task-def-formation.yml b/dist/cloud-formations/task-def-formation.yml deleted file mode 100644 index 79e783f8..00000000 --- a/dist/cloud-formations/task-def-formation.yml +++ /dev/null @@ -1,221 +0,0 @@ -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. -Parameters: - EnvironmentName: - Type: String - Default: development - Description: 'Your deployment environment: DEV, QA , PROD' - ServiceName: - Type: String - Default: example - Description: A name for the service - ImageUrl: - Type: String - Default: nginx - Description: >- - The url of a docker image that contains the application process that will - handle the traffic for this service - ContainerPort: - Type: Number - Default: 80 - Description: What port number the application inside the docker container is binding to - ContainerCpu: - Type: Number - Default: 1024 - Description: How much CPU to give the container. 1024 is 1 CPU - ContainerMemory: - Type: Number - Default: 2048 - Description: How much memory in megabytes to give the container - BUILDGUID: - Type: String - Default: '' - Command: - Type: String - Default: 'ls' - EntryPoint: - Type: String - Default: '/bin/sh' - WorkingDirectory: - Type: String - Default: '/efsdata/' - Role: - Type: String - Default: '' - Description: >- - (Optional) An IAM role to give the service's containers if the code within - needs to access other AWS resources - EFSMountDirectory: - Type: String - Default: '/efsdata' - # template secrets p1 - input -Mappings: - SubnetConfig: - VPC: - CIDR: 10.0.0.0/16 - PublicOne: - CIDR: 10.0.0.0/24 - PublicTwo: - CIDR: 10.0.1.0/24 -Conditions: - HasCustomRole: !Not - - !Equals - - Ref: Role - - '' -Resources: - LogGroup: - Type: 'AWS::Logs::LogGroup' - Properties: - LogGroupName: !Ref ServiceName - Metadata: - 'AWS::CloudFormation::Designer': - id: aece53ae-b82d-4267-bc16-ed964b05db27 - SubscriptionFilter: - Type: 'AWS::Logs::SubscriptionFilter' - Properties: - FilterPattern: '' - RoleArn: - 'Fn::ImportValue': !Sub '${EnvironmentName}:CloudWatchIAMRole' - LogGroupName: !Ref ServiceName - DestinationArn: - 'Fn::GetAtt': - - KinesisStream - - Arn - Metadata: - 'AWS::CloudFormation::Designer': - id: 7f809e91-9e5d-4678-98c1-c5085956c480 - DependsOn: - - LogGroup - - KinesisStream - KinesisStream: - Type: 'AWS::Kinesis::Stream' - Properties: - Name: !Ref ServiceName - ShardCount: 1 - Metadata: - 'AWS::CloudFormation::Designer': - id: c6f18447-b879-4696-8873-f981b2cedd2b - - # template secrets p2 - secret - - TaskDefinition: - Type: 'AWS::ECS::TaskDefinition' - Properties: - Family: !Ref ServiceName - Cpu: !Ref ContainerCpu - Memory: !Ref ContainerMemory - NetworkMode: awsvpc - Volumes: - - Name: efs-data - EFSVolumeConfiguration: - FilesystemId: - 'Fn::ImportValue': !Sub '${EnvironmentName}:EfsFileStorageId' - TransitEncryption: ENABLED - RequiresCompatibilities: - - FARGATE - ExecutionRoleArn: - 'Fn::ImportValue': !Sub '${EnvironmentName}:ECSTaskExecutionRole' - TaskRoleArn: - 'Fn::If': - - HasCustomRole - - !Ref Role - - !Ref 'AWS::NoValue' - ContainerDefinitions: - - Name: !Ref ServiceName - Cpu: !Ref ContainerCpu - Memory: !Ref ContainerMemory - Image: !Ref ImageUrl - EntryPoint: - Fn::Split: - - "," - - !Ref EntryPoint - Command: - Fn::Split: - - "," - - !Ref Command - WorkingDirectory: !Ref WorkingDirectory - Environment: - - Name: ALLOW_EMPTY_PASSWORD - Value: 'yes' - # template - env vars - MountPoints: - - SourceVolume: efs-data - ContainerPath: !Ref EFSMountDirectory - ReadOnly: false - Secrets: - # template secrets p3 - container def - LogConfiguration: - LogDriver: awslogs - Options: - awslogs-group: !Ref ServiceName - awslogs-region: !Ref 'AWS::Region' - awslogs-stream-prefix: !Ref ServiceName - Metadata: - 'AWS::CloudFormation::Designer': - id: dabb0116-abe0-48a6-a8af-cf9111c879a5 - DependsOn: - - LogGroup -Metadata: - 'AWS::CloudFormation::Designer': - dabb0116-abe0-48a6-a8af-cf9111c879a5: - size: - width: 60 - height: 60 - position: - x: 270 - 'y': 90 - z: 1 - embeds: [] - dependson: - - aece53ae-b82d-4267-bc16-ed964b05db27 - c6f18447-b879-4696-8873-f981b2cedd2b: - size: - width: 60 - height: 60 - position: - x: 270 - 'y': 210 - z: 1 - embeds: [] - 7f809e91-9e5d-4678-98c1-c5085956c480: - size: - width: 60 - height: 60 - position: - x: 60 - 'y': 300 - z: 1 - embeds: [] - dependson: - - aece53ae-b82d-4267-bc16-ed964b05db27 - - c6f18447-b879-4696-8873-f981b2cedd2b - aece53ae-b82d-4267-bc16-ed964b05db27: - size: - width: 150 - height: 150 - position: - x: 60 - 'y': 90 - z: 1 - embeds: [] - 4d2da56c-3643-46b8-aaee-e46e19f95fcc: - source: - id: 7f809e91-9e5d-4678-98c1-c5085956c480 - target: - id: aece53ae-b82d-4267-bc16-ed964b05db27 - z: 11 - 14eb957b-f094-4653-93c4-77b2f851953c: - source: - id: 7f809e91-9e5d-4678-98c1-c5085956c480 - target: - id: c6f18447-b879-4696-8873-f981b2cedd2b - z: 12 - 85c57444-e5bb-4230-bc85-e545cd4558f6: - source: - id: dabb0116-abe0-48a6-a8af-cf9111c879a5 - target: - id: aece53ae-b82d-4267-bc16-ed964b05db27 - z: 13 diff --git a/dist/index.js b/dist/index.js index f577595c61e0e6a2d96fcad78475ee9745afbb85..476b3bcdeea6d6b34c7ab83a8476ec6fb81d7c4d 100644 GIT binary patch delta 21320 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 diff --git a/dist/index.js.map b/dist/index.js.map index 1c9e74e8a555ec6ef362e87923e77c635b75444b..a161782859051d9f94f19a82d1142dafc595dcd4 100644 GIT binary patch delta 22192 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 Ie)GNm7c{G9-2eap delta 2751 zcmZ|PdsNiN6$kLMu*Dg0-#2R>h!>h-pb}G%;#W6Q2kgiyEK!h&D0Ce1D6zJ?-fqpFQ*X&D^;& zvyBH@jT>&Y80*$rsxl00DG62TlST#Vv`X#KV1*kuNA9i4NL^7m*00_fG}H?PMc!U( zdR4DA$syZ)gLGWE*rdqMCIoc#;uMQT;UhIcd+TZTT`#tG1Szt{9d^o#o5}-}dUMhM ze{JrjFtZ}(M1}O$CiLTmuY(J;LiIiWqfFyX3fsr{M|-+rz6o7fl~LhotsgvOLx`vK zkRhb9`f6ZmedAnzou}nUY~(PlQ2)*lFC7+N>h1aDjX{}<24(8N3%o%O2JitR_<{-i zK!Gmc4*_5X3j{(C1VadfLKuWY1VlntIVdw~(@sv`Z;e=VHY6Hipqo4!VjaKFH(}gd zm)lWb=fEB!ic?NnC-IikRzJD*l(jvcSCrZ^`BsZ+kk>bH(^>~Q9-sluW*(tE}r71X1g2-E{D6=nK-u~KdvX5irv%t^Y-Z! zELR(8XK;0e>LcwV#7b|DZc#^ZtCgZzMp2-)Y!BHzn(W?OyXLZn>Y;V4`xz4uG%Bv!_PWZmCdJOja-gQ zRNuyO&eMoyTsD>X$@?>DpU#WQI5~^L`9cAW;8d#!X3EA&&GX6lbP|7FT4M|0+F3MK z>I!L`j&q!p(K*^Pu*HeEE^M;)lKp1WKs|p+wqUukn8Ng&l&D5><=IRVTiRpw{AmeA z$mA00spFb?n8Ue17^Sm}8anz?KFm4j|yMec8PYQjtjuFCf3AR^N`+-f8rACx=$al<&p4{)eoqL zt~2NM+*JlPPi`@A_^PY;E=HJn?YHzCr+h;X|L?I#q+_6W5d85jeyTzP96ztj?%E``|?jWss z>ow7hn|BLUPH7PD>AXtvxO9hzV`rO4;K_}`mv8S8p+4G#?m{lxA)e7jcjse`qVNAs zC!_a@m-J?gj-^tXjz>-j$#z~JU<>QK@O9s+eoSfj+1ey*Mhw(mIaj@`hV!i^@wOJK zEd5Yi_x1$+dW4AK=CqT(vhpJluk*A8ZnQ;lb1ts_h5cfwD4wOohRw##^XzJzBj1su z{eP4tdOC8ooktmq9IoPgw~M#$7S_&Cv>nRp2SmLW*PRu;dGsY=(zX@CR}P9a4%#Im zp2U2gd^qd;J%ikQP^|Ov1W|fOJm1+{>$XM7O^3z7aHc|G<2&uhPR9V74-ZaJ6SP^e z-VqTcS6>o4Dm))&xYSUCCvKVRQgwxVtEVlEYnQ3M{K7@Gj2~ZA7irJbpI>}e^p*qK z)m#IgUndOQdQ~meF20@zHwb^OO;x*c;Y~GMTCS^2y8r5t=&3XQ$K;0}szH2jpEZap zr>b4J`3!dW_$#$Pzm}u+;f@Dtg~SzpU(e?rsU#DWip|O+O*VxM zF}4(wr@3F>q)~iSv2|NCHO2<9&>fzEIOqXAp%?rJ;voSN;aNz6WOxpGLm%i1KZbtL zAASM@U?2>FpThGn7*Ze=hQLrrgJCcnUVsrS};LorHw!${3gYED>)WZ(g3A;eT zZfJl#&0(=P<;UCZrm*6s7fv?~yT!ZUy18%}C=z!aB2kyc>_!{oZ KsWA^GE&Kt+&L~p= diff --git a/src/model/cloud-runner/cloud-runner-statics.ts b/src/model/cloud-runner/cloud-runner-statics.ts index 964a8980..afebab9b 100644 --- a/src/model/cloud-runner/cloud-runner-statics.ts +++ b/src/model/cloud-runner/cloud-runner-statics.ts @@ -1,3 +1,3 @@ export class CloudRunnerStatics { - public static readonly logPrefix = `Cloud-Runner-System`; + public static readonly logPrefix = `Cloud-Runner`; } diff --git a/src/model/cloud-runner/providers/aws/aws-base-stack.ts b/src/model/cloud-runner/providers/aws/aws-base-stack.ts index 74ee166c..a60cc43a 100644 --- a/src/model/cloud-runner/providers/aws/aws-base-stack.ts +++ b/src/model/cloud-runner/providers/aws/aws-base-stack.ts @@ -1,8 +1,7 @@ import CloudRunnerLogger from '../../services/cloud-runner-logger'; import * as core from '@actions/core'; import * as SDK from 'aws-sdk'; -import * as fs from 'fs'; -import path from 'path'; +import { BaseStackFormation } from './cloud-formations/base-stack-formation'; const crypto = require('crypto'); export class AWSBaseStack { @@ -14,7 +13,7 @@ export class AWSBaseStack { async setupBaseStack(CF: SDK.CloudFormation) { const baseStackName = this.baseStackName; - const baseStack = fs.readFileSync(path.join(__dirname, 'cloud-formations', 'base-setup.yml'), 'utf8'); + const baseStack = BaseStackFormation.formation; // Cloud Formation Input const describeStackInput: SDK.CloudFormation.DescribeStacksInput = { diff --git a/src/model/cloud-runner/providers/aws/aws-cloud-formation-templates.ts b/src/model/cloud-runner/providers/aws/aws-cloud-formation-templates.ts index 91f5a6c7..0d0f83a4 100644 --- a/src/model/cloud-runner/providers/aws/aws-cloud-formation-templates.ts +++ b/src/model/cloud-runner/providers/aws/aws-cloud-formation-templates.ts @@ -1,4 +1,4 @@ -import * as fs from 'fs'; +import { TaskDefinitionFormation } from './cloud-formations/task-definition-formation'; export class AWSCloudFormationTemplates { public static getParameterTemplate(p1) { @@ -34,6 +34,6 @@ export class AWSCloudFormationTemplates { } public static readTaskCloudFormationTemplate(): string { - return fs.readFileSync(`${__dirname}/cloud-formations/task-def-formation.yml`, 'utf8'); + return TaskDefinitionFormation.formation; } } diff --git a/src/model/cloud-runner/providers/aws/aws-job-stack.ts b/src/model/cloud-runner/providers/aws/aws-job-stack.ts index 056d6d6a..c970faa9 100644 --- a/src/model/cloud-runner/providers/aws/aws-job-stack.ts +++ b/src/model/cloud-runner/providers/aws/aws-job-stack.ts @@ -4,6 +4,7 @@ import CloudRunnerSecret from '../../services/cloud-runner-secret'; import { AWSCloudFormationTemplates } from './aws-cloud-formation-templates'; import CloudRunnerLogger from '../../services/cloud-runner-logger'; import { AWSError } from './aws-error'; +import CloudRunner from '../../cloud-runner'; export class AWSJobStack { private baseStackName: string; @@ -23,6 +24,20 @@ export class AWSJobStack { ): Promise { const taskDefStackName = `${this.baseStackName}-${buildGuid}`; let taskDefCloudFormation = AWSCloudFormationTemplates.readTaskCloudFormationTemplate(); + const cpu = CloudRunner.buildParameters.cloudRunnerCpu || '1024'; + const memory = CloudRunner.buildParameters.cloudRunnerMemory || '3072'; + taskDefCloudFormation = taskDefCloudFormation.replace( + `ContainerCpu: + Default: 1024`, + `ContainerCpu: + Default: ${Number.parseInt(cpu)}`, + ); + taskDefCloudFormation = taskDefCloudFormation.replace( + `ContainerMemory: + Default: 2048`, + `ContainerMemory: + Default: ${Number.parseInt(memory)}`, + ); for (const secret of secrets) { secret.ParameterKey = `${buildGuid.replace(/[^\dA-Za-z]/g, '')}${secret.ParameterKey.replace( /[^\dA-Za-z]/g, @@ -85,7 +100,9 @@ export class AWSJobStack { }, ...secretsMappedToCloudFormationParameters, ]; - + CloudRunnerLogger.log( + `Starting AWS job with memory: ${CloudRunner.buildParameters.cloudRunnerMemory} cpu: ${CloudRunner.buildParameters.cloudRunnerCpu}`, + ); let previousStackExists = true; while (previousStackExists) { previousStackExists = false; @@ -101,14 +118,15 @@ export class AWSJobStack { } } } + const createStackInput: SDK.CloudFormation.CreateStackInput = { + StackName: taskDefStackName, + TemplateBody: taskDefCloudFormation, + Capabilities: ['CAPABILITY_IAM'], + Parameters: parameters, + }; try { - await CF.createStack({ - StackName: taskDefStackName, - TemplateBody: taskDefCloudFormation, - Capabilities: ['CAPABILITY_IAM'], - Parameters: parameters, - }).promise(); + await CF.createStack(createStackInput).promise(); CloudRunnerLogger.log('Creating cloud runner job'); await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise(); } catch (error) { 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 772c2644..8b88909b 100644 --- a/src/model/cloud-runner/providers/aws/aws-task-runner.ts +++ b/src/model/cloud-runner/providers/aws/aws-task-runner.ts @@ -58,14 +58,21 @@ class AWSTaskRunner { CloudRunnerLogger.log( `Cloud runner job status is running ${(await AWSTaskRunner.describeTasks(ECS, cluster, taskArn))?.lastStatus}`, ); - const output = await this.streamLogsUntilTaskStops(ECS, CF, taskDef, cluster, taskArn, streamName); + const { output, shouldCleanup } = await this.streamLogsUntilTaskStops( + ECS, + CF, + taskDef, + cluster, + taskArn, + streamName, + ); const taskData = await AWSTaskRunner.describeTasks(ECS, cluster, taskArn); const exitCode = taskData.containers?.[0].exitCode; const wasSuccessful = exitCode === 0 || (exitCode === undefined && taskData.lastStatus === 'RUNNING'); if (wasSuccessful) { CloudRunnerLogger.log(`Cloud runner job has finished successfully`); - return output; + return { output, shouldCleanup }; } else { if (taskData.stoppedReason === 'Essential container in task exited' && exitCode === 1) { throw new Error('Container exited with code 1'); @@ -122,22 +129,24 @@ class AWSTaskRunner { 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}`); let shouldReadLogs = true; + let shouldCleanup = true; let timestamp: number = 0; let output = ''; while (shouldReadLogs) { await new Promise((resolve) => setTimeout(resolve, 1500)); const taskData = await AWSTaskRunner.describeTasks(ECS, clusterName, taskArn); ({ timestamp, shouldReadLogs } = AWSTaskRunner.checkStreamingShouldContinue(taskData, timestamp, shouldReadLogs)); - ({ iterator, shouldReadLogs, output } = await AWSTaskRunner.handleLogStreamIteration( + ({ iterator, shouldReadLogs, output, shouldCleanup } = await AWSTaskRunner.handleLogStreamIteration( kinesis, iterator, shouldReadLogs, taskDef, output, + shouldCleanup, )); } - return output; + return { output, shouldCleanup }; } private static async handleLogStreamIteration( @@ -146,6 +155,7 @@ class AWSTaskRunner { shouldReadLogs: boolean, taskDef: CloudRunnerAWSTaskDef, output: string, + shouldCleanup: boolean, ) { const records = await kinesis .getRecords({ @@ -153,9 +163,16 @@ class AWSTaskRunner { }) .promise(); iterator = records.NextShardIterator || ''; - ({ shouldReadLogs, output } = AWSTaskRunner.logRecords(records, iterator, taskDef, shouldReadLogs, output)); + ({ shouldReadLogs, output, shouldCleanup } = AWSTaskRunner.logRecords( + records, + iterator, + taskDef, + shouldReadLogs, + output, + shouldCleanup, + )); - return { iterator, shouldReadLogs, output }; + return { iterator, shouldReadLogs, output, shouldCleanup }; } private static checkStreamingShouldContinue(taskData: AWS.ECS.Task, timestamp: number, shouldReadLogs: boolean) { @@ -183,6 +200,7 @@ class AWSTaskRunner { taskDef: CloudRunnerAWSTaskDef, shouldReadLogs: boolean, output: string, + shouldCleanup: boolean, ) { if (records.Records.length > 0 && iterator) { for (let index = 0; index < records.Records.length; index++) { @@ -197,11 +215,18 @@ class AWSTaskRunner { 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) { @@ -213,7 +238,7 @@ class AWSTaskRunner { } } - return { shouldReadLogs, output }; + return { shouldReadLogs, output, shouldCleanup }; } private static async getLogStream(kinesis: AWS.Kinesis, kinesisStreamName: string) { diff --git a/src/model/cloud-runner/providers/aws/cloud-formations/base-setup.yml b/src/model/cloud-runner/providers/aws/cloud-formations/base-stack-formation.ts similarity index 93% rename from src/model/cloud-runner/providers/aws/cloud-formations/base-setup.yml rename to src/model/cloud-runner/providers/aws/cloud-formations/base-stack-formation.ts index 3941aeb0..4edd4047 100644 --- a/src/model/cloud-runner/providers/aws/cloud-formations/base-setup.yml +++ b/src/model/cloud-runner/providers/aws/cloud-formations/base-stack-formation.ts @@ -1,7 +1,6 @@ -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. +export class BaseStackFormation { + public static readonly formation: string = `AWSTemplateFormatVersion: '2010-09-09' +Description: Game-CI base stack Parameters: EnvironmentName: Type: String @@ -335,57 +334,58 @@ Outputs: Description: 'The connection endpoint for the database.' Value: !Ref EfsFileStorage Export: - Name: !Sub ${EnvironmentName}:EfsFileStorageId + Name: !Sub ${'${EnvironmentName}'}:EfsFileStorageId ClusterName: Description: The name of the ECS cluster Value: !Ref 'ECSCluster' Export: - Name: !Sub ${EnvironmentName}:ClusterName + Name: !Sub${' ${EnvironmentName}'}:ClusterName AutoscalingRole: Description: The ARN of the role used for autoscaling Value: !GetAtt 'AutoscalingRole.Arn' Export: - Name: !Sub ${EnvironmentName}:AutoscalingRole + Name: !Sub ${'${EnvironmentName}'}:AutoscalingRole ECSRole: Description: The ARN of the ECS role Value: !GetAtt 'ECSRole.Arn' Export: - Name: !Sub ${EnvironmentName}:ECSRole + Name: !Sub ${'${EnvironmentName}'}:ECSRole ECSTaskExecutionRole: Description: The ARN of the ECS role tsk execution role Value: !GetAtt 'ECSTaskExecutionRole.Arn' Export: - Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole + Name: !Sub ${'${EnvironmentName}'}:ECSTaskExecutionRole DeleteCFNLambdaExecutionRole: Description: Lambda execution role for cleaning up cloud formations Value: !GetAtt 'DeleteCFNLambdaExecutionRole.Arn' Export: - Name: !Sub ${EnvironmentName}:DeleteCFNLambdaExecutionRole + Name: !Sub ${'${EnvironmentName}'}:DeleteCFNLambdaExecutionRole CloudWatchIAMRole: Description: The ARN of the CloudWatch role for subscription filter Value: !GetAtt 'CloudWatchIAMRole.Arn' Export: - Name: !Sub ${EnvironmentName}:CloudWatchIAMRole + Name: !Sub ${'${EnvironmentName}'}:CloudWatchIAMRole VpcId: Description: The ID of the VPC that this stack is deployed in Value: !Ref 'VPC' Export: - Name: !Sub ${EnvironmentName}:VpcId + Name: !Sub ${'${EnvironmentName}'}:VpcId PublicSubnetOne: Description: Public subnet one Value: !Ref 'PublicSubnetOne' Export: - Name: !Sub ${EnvironmentName}:PublicSubnetOne + Name: !Sub ${'${EnvironmentName}'}:PublicSubnetOne PublicSubnetTwo: Description: Public subnet two Value: !Ref 'PublicSubnetTwo' Export: - Name: !Sub ${EnvironmentName}:PublicSubnetTwo - + Name: !Sub ${'${EnvironmentName}'}:PublicSubnetTwo ContainerSecurityGroup: Description: A security group used to allow Fargate containers to receive traffic Value: !Ref 'ContainerSecurityGroup' Export: - Name: !Sub ${EnvironmentName}:ContainerSecurityGroup + Name: !Sub ${'${EnvironmentName}'}:ContainerSecurityGroup +`; +} diff --git a/src/model/cloud-runner/providers/aws/cloud-formations/task-def-formation.yml b/src/model/cloud-runner/providers/aws/cloud-formations/task-definition-formation.ts similarity index 69% rename from src/model/cloud-runner/providers/aws/cloud-formations/task-def-formation.yml rename to src/model/cloud-runner/providers/aws/cloud-formations/task-definition-formation.ts index fae6cf4e..49669c4e 100644 --- a/src/model/cloud-runner/providers/aws/cloud-formations/task-def-formation.yml +++ b/src/model/cloud-runner/providers/aws/cloud-formations/task-definition-formation.ts @@ -1,4 +1,5 @@ -AWSTemplateFormatVersion: 2010-09-09 +export class TaskDefinitionFormation { + 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 @@ -23,12 +24,12 @@ Parameters: Default: 80 Description: What port number the application inside the docker container is binding to ContainerCpu: - Type: Number Default: 1024 + Type: Number Description: How much CPU to give the container. 1024 is 1 CPU ContainerMemory: - Type: Number Default: 2048 + Type: Number Description: How much memory in megabytes to give the container BUILDGUID: Type: String @@ -78,7 +79,7 @@ Resources: Properties: FilterPattern: '' RoleArn: - 'Fn::ImportValue': !Sub '${EnvironmentName}:CloudWatchIAMRole' + 'Fn::ImportValue': !Sub '${'${EnvironmentName}'}:CloudWatchIAMRole' LogGroupName: !Ref ServiceName DestinationArn: 'Fn::GetAtt': @@ -98,9 +99,7 @@ Resources: Metadata: 'AWS::CloudFormation::Designer': id: c6f18447-b879-4696-8873-f981b2cedd2b - # template secrets p2 - secret - TaskDefinition: Type: 'AWS::ECS::TaskDefinition' Properties: @@ -112,12 +111,12 @@ Resources: - Name: efs-data EFSVolumeConfiguration: FilesystemId: - 'Fn::ImportValue': !Sub '${EnvironmentName}:EfsFileStorageId' + 'Fn::ImportValue': !Sub '${'${EnvironmentName}'}:EfsFileStorageId' TransitEncryption: ENABLED RequiresCompatibilities: - FARGATE ExecutionRoleArn: - 'Fn::ImportValue': !Sub '${EnvironmentName}:ECSTaskExecutionRole' + 'Fn::ImportValue': !Sub '${'${EnvironmentName}'}:ECSTaskExecutionRole' TaskRoleArn: 'Fn::If': - HasCustomRole @@ -153,69 +152,7 @@ Resources: awslogs-group: !Ref ServiceName awslogs-region: !Ref 'AWS::Region' awslogs-stream-prefix: !Ref ServiceName - Metadata: - 'AWS::CloudFormation::Designer': - id: dabb0116-abe0-48a6-a8af-cf9111c879a5 DependsOn: - LogGroup -Metadata: - 'AWS::CloudFormation::Designer': - dabb0116-abe0-48a6-a8af-cf9111c879a5: - size: - width: 60 - height: 60 - position: - x: 270 - 'y': 90 - z: 1 - embeds: [] - dependson: - - aece53ae-b82d-4267-bc16-ed964b05db27 - c6f18447-b879-4696-8873-f981b2cedd2b: - size: - width: 60 - height: 60 - position: - x: 270 - 'y': 210 - z: 1 - embeds: [] - 7f809e91-9e5d-4678-98c1-c5085956c480: - size: - width: 60 - height: 60 - position: - x: 60 - 'y': 300 - z: 1 - embeds: [] - dependson: - - aece53ae-b82d-4267-bc16-ed964b05db27 - - c6f18447-b879-4696-8873-f981b2cedd2b - aece53ae-b82d-4267-bc16-ed964b05db27: - size: - width: 150 - height: 150 - position: - x: 60 - 'y': 90 - z: 1 - embeds: [] - 4d2da56c-3643-46b8-aaee-e46e19f95fcc: - source: - id: 7f809e91-9e5d-4678-98c1-c5085956c480 - target: - id: aece53ae-b82d-4267-bc16-ed964b05db27 - z: 11 - 14eb957b-f094-4653-93c4-77b2f851953c: - source: - id: 7f809e91-9e5d-4678-98c1-c5085956c480 - target: - id: c6f18447-b879-4696-8873-f981b2cedd2b - z: 12 - 85c57444-e5bb-4230-bc85-e545cd4558f6: - source: - id: dabb0116-abe0-48a6-a8af-cf9111c879a5 - target: - id: aece53ae-b82d-4267-bc16-ed964b05db27 - z: 13 +`; +} 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 3c0d237b..deb6f9ce 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 @@ -4,27 +4,89 @@ import Input from '../../../../input'; import CloudRunnerLogger from '../../../services/cloud-runner-logger'; export class AwsCliCommands { - @CliFunction(`aws-garbage-collect`, `garbage collect aws`) - static async garbageCollectAws() { + @CliFunction(`aws-list-stacks`, `List stacks`) + static async awsListStacks(perResultCallback: any) { process.env.AWS_REGION = Input.region; - CloudRunnerLogger.log(`Cloud Formation stacks`); const CF = new AWS.CloudFormation(); const stacks = (await CF.listStacks().promise()).StackSummaries?.filter((_x) => _x.StackStatus !== 'DELETE_COMPLETE') || []; + CloudRunnerLogger.log(`DescribeStacksRequest ${stacks.length}`); for (const element of stacks) { CloudRunnerLogger.log(JSON.stringify(element, undefined, 4)); + CloudRunnerLogger.log(`${element.StackName}`); + if (perResultCallback) await perResultCallback(element); } - CloudRunnerLogger.log(`ECS Clusters`); - const ecs = new AWS.ECS(); - const clusters = (await ecs.listClusters().promise()).clusterArns || []; if (stacks === undefined) { return; } + } + @CliFunction(`aws-list-tasks`, `List tasks`) + static async awsListTasks(perResultCallback: any) { + process.env.AWS_REGION = Input.region; + CloudRunnerLogger.log(`ECS Clusters`); + const ecs = new AWS.ECS(); + const clusters = (await ecs.listClusters().promise()).clusterArns || []; for (const element of clusters) { const input: AWS.ECS.ListTasksRequest = { cluster: element, }; - CloudRunnerLogger.log(JSON.stringify(await ecs.listTasks(input).promise(), undefined, 4)); + const list = (await ecs.listTasks(input).promise()).taskArns || []; + if (list.length > 0) { + const describeInput: AWS.ECS.DescribeTasksRequest = { tasks: list, cluster: element }; + const describeList = (await ecs.describeTasks(describeInput).promise()).tasks || []; + if (describeList === []) { + continue; + } + CloudRunnerLogger.log(`DescribeTasksRequest ${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; + } + if (perResultCallback) await perResultCallback(taskElement, element); + } + } } } + + @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); + } + + private static async cleanup(deleteResources = false) { + process.env.AWS_REGION = Input.region; + const CF = new AWS.CloudFormation(); + const ecs = new AWS.ECS(); + await AwsCliCommands.awsListStacks(async (element) => { + if (deleteResources) { + if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') { + CloudRunnerLogger.log(`Skipping ${element.StackName} ignore list`); + + return; + } + const deleteStackInput: AWS.CloudFormation.DeleteStackInput = { StackName: element.StackName }; + await CF.deleteStack(deleteStackInput).promise(); + } + }); + await AwsCliCommands.awsListTasks(async (taskElement, element) => { + if (deleteResources) { + await ecs.stopTask({ task: taskElement.taskArn || '', cluster: element }).promise(); + } + }); + } } diff --git a/src/model/cloud-runner/providers/aws/index.ts b/src/model/cloud-runner/providers/aws/index.ts index 4d8fca31..4be2f66f 100644 --- a/src/model/cloud-runner/providers/aws/index.ts +++ b/src/model/cloud-runner/providers/aws/index.ts @@ -66,21 +66,24 @@ class AWSBuildEnvironment implements ProviderInterface { ); let postRunTaskTimeMs; - let output = ''; try { const postSetupStacksTimeMs = Date.now(); CloudRunnerLogger.log(`Setup job time: ${Math.floor((postSetupStacksTimeMs - startTimeMs) / 1000)}s`); - output = await AWSTaskRunner.runTask(taskDef, ECS, CF, environment, buildGuid, commands); + const { output, shouldCleanup } = await AWSTaskRunner.runTask(taskDef, ECS, CF, environment, buildGuid, commands); postRunTaskTimeMs = Date.now(); CloudRunnerLogger.log(`Run job time: ${Math.floor((postRunTaskTimeMs - postSetupStacksTimeMs) / 1000)}s`); - } finally { - await this.cleanupResources(CF, taskDef); + if (shouldCleanup) { + await this.cleanupResources(CF, taskDef); + } const postCleanupTimeMs = Date.now(); if (postRunTaskTimeMs !== undefined) CloudRunnerLogger.log(`Cleanup job time: ${Math.floor((postCleanupTimeMs - postRunTaskTimeMs) / 1000)}s`); - } - return output; + return output; + } catch (error) { + await this.cleanupResources(CF, taskDef); + throw error; + } } async cleanupResources(CF: SDK.CloudFormation, taskDef: CloudRunnerAWSTaskDef) { diff --git a/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts b/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts index 8e7cf026..0bb9294a 100644 --- a/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts +++ b/src/model/cloud-runner/providers/k8s/kubernetes-job-spec-factory.ts @@ -108,8 +108,8 @@ class KubernetesJobSpecFactory { workingDir: `${workingDirectory}`, resources: { requests: { - memory: buildParameters.cloudRunnerMemory, - cpu: buildParameters.cloudRunnerCpu, + memory: buildParameters.cloudRunnerMemory || '750M', + cpu: buildParameters.cloudRunnerCpu || '1', }, }, env: [ diff --git a/src/model/cloud-runner/remote-client/caching.ts b/src/model/cloud-runner/remote-client/caching.ts index 277353c3..19cdbf03 100644 --- a/src/model/cloud-runner/remote-client/caching.ts +++ b/src/model/cloud-runner/remote-client/caching.ts @@ -70,17 +70,17 @@ export class Caching { return typeof arguments_[number] != 'undefined' ? arguments_[number] : match; }); }; - await CloudRunnerSystem.Run(`zip -q -r ${cacheArtifactName}.zip ${path.basename(sourceFolder)}`); - assert(await fileExists(`${cacheArtifactName}.zip`), 'cache zip exists'); + await CloudRunnerSystem.Run(`tar -cf ${cacheArtifactName}.tar ${path.basename(sourceFolder)}`); + assert(await fileExists(`${cacheArtifactName}.tar`), 'cache archive exists'); assert(await fileExists(path.basename(sourceFolder)), 'source folder exists'); if (CloudRunner.buildParameters.cachePushOverrideCommand) { await CloudRunnerSystem.Run(formatFunction(CloudRunner.buildParameters.cachePushOverrideCommand)); } - await CloudRunnerSystem.Run(`mv ${cacheArtifactName}.zip ${cacheFolder}`); - RemoteClientLogger.log(`moved ${cacheArtifactName}.zip to ${cacheFolder}`); + await CloudRunnerSystem.Run(`mv ${cacheArtifactName}.tar ${cacheFolder}`); + RemoteClientLogger.log(`moved cache entry ${cacheArtifactName} to ${cacheFolder}`); assert( - await fileExists(`${path.join(cacheFolder, cacheArtifactName)}.zip`), - 'cache zip exists inside cache folder', + await fileExists(`${path.join(cacheFolder, cacheArtifactName)}.tar`), + 'cache archive exists inside cache folder', ); } catch (error) { process.chdir(`${startPath}`); @@ -101,14 +101,14 @@ export class Caching { await fs.promises.mkdir(destinationFolder); } - const latestInBranch = await (await CloudRunnerSystem.Run(`ls -t "${cacheFolder}" | grep .zip$ | head -1`)) + const latestInBranch = await (await CloudRunnerSystem.Run(`ls -t "${cacheFolder}" | grep .tar$ | head -1`)) .replace(/\n/g, ``) - .replace('.zip', ''); + .replace('.tar', ''); process.chdir(cacheFolder); const cacheSelection = - cacheArtifactName !== `` && (await fileExists(`${cacheArtifactName}.zip`)) ? cacheArtifactName : latestInBranch; + cacheArtifactName !== `` && (await fileExists(`${cacheArtifactName}.tar`)) ? cacheArtifactName : latestInBranch; await CloudRunnerLogger.log(`cache key ${cacheArtifactName} selection ${cacheSelection}`); // eslint-disable-next-line func-style @@ -127,12 +127,12 @@ export class Caching { await CloudRunnerSystem.Run(formatFunction(CloudRunner.buildParameters.cachePullOverrideCommand)); } - if (await fileExists(`${cacheSelection}.zip`)) { + if (await fileExists(`${cacheSelection}.tar`)) { const resultsFolder = `results${CloudRunner.buildParameters.buildGuid}`; await CloudRunnerSystem.Run(`mkdir -p ${resultsFolder}`); - RemoteClientLogger.log(`cache item exists ${cacheFolder}/${cacheSelection}.zip`); + RemoteClientLogger.log(`cache item exists ${cacheFolder}/${cacheSelection}.tar`); const fullResultsFolder = path.join(cacheFolder, resultsFolder); - await CloudRunnerSystem.Run(`unzip -q ${cacheSelection}.zip -d ${path.basename(resultsFolder)}`); + await CloudRunnerSystem.Run(`tar -xf ${cacheSelection}.tar -C ${fullResultsFolder}`); RemoteClientLogger.log(`cache item extracted to ${fullResultsFolder}`); assert(await fileExists(fullResultsFolder), `cache extraction results folder exists`); const destinationParentFolder = path.resolve(destinationFolder, '..'); @@ -143,7 +143,6 @@ export class Caching { await CloudRunnerSystem.Run( `mv "${path.join(fullResultsFolder, path.basename(destinationFolder))}" "${destinationParentFolder}"`, ); - await CloudRunnerSystem.Run(`du -sh ${path.join(destinationParentFolder, path.basename(destinationFolder))}`); const contents = await fs.promises.readdir( path.join(destinationParentFolder, path.basename(destinationFolder)), ); @@ -153,7 +152,7 @@ export class Caching { } else { RemoteClientLogger.logWarning(`cache item ${cacheArtifactName} doesn't exist ${destinationFolder}`); if (cacheSelection !== ``) { - RemoteClientLogger.logWarning(`cache item ${cacheArtifactName}.zip doesn't exist ${destinationFolder}`); + RemoteClientLogger.logWarning(`cache item ${cacheArtifactName}.tar doesn't exist ${destinationFolder}`); throw new Error(`Failed to get cache item, but cache hit was found: ${cacheSelection}`); } } diff --git a/src/model/cloud-runner/remote-client/index.ts b/src/model/cloud-runner/remote-client/index.ts index bf80fbc7..39cfcf70 100644 --- a/src/model/cloud-runner/remote-client/index.ts +++ b/src/model/cloud-runner/remote-client/index.ts @@ -45,8 +45,10 @@ export class RemoteClient { } private static async sizeOfFolder(message: string, folder: string) { - CloudRunnerLogger.log(`Size of ${message}`); - await CloudRunnerSystem.Run(`du -sh ${folder}`); + if (CloudRunner.buildParameters.cloudRunnerIntegrationTests) { + CloudRunnerLogger.log(`Size of ${message}`); + await CloudRunnerSystem.Run(`du -sh ${folder}`); + } } private static async cloneRepoWithoutLFSFiles() { diff --git a/src/model/cloud-runner/workflows/build-automation-workflow.ts b/src/model/cloud-runner/workflows/build-automation-workflow.ts index 3552dff8..ac02ef9a 100644 --- a/src/model/cloud-runner/workflows/build-automation-workflow.ts +++ b/src/model/cloud-runner/workflows/build-automation-workflow.ts @@ -71,7 +71,7 @@ export class BuildAutomationWorkflow implements WorkflowInterface { const builderPath = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', `index.js`).replace(/\\/g, `/`); return `apt-get update > /dev/null - apt-get install -y zip tree npm git-lfs jq unzip git > /dev/null + apt-get install -y tar tree npm git-lfs jq git > /dev/null npm install -g n > /dev/null n stable > /dev/null ${setupHooks.filter((x) => x.hook.includes(`before`)).map((x) => x.commands) || ' '} diff --git a/src/model/input-readers/generic-input-reader.ts b/src/model/input-readers/generic-input-reader.ts index 0bd84e11..85a42fea 100644 --- a/src/model/input-readers/generic-input-reader.ts +++ b/src/model/input-readers/generic-input-reader.ts @@ -1,7 +1,12 @@ import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system'; +import Input from '../input'; export class GenericInputReader { public static async Run(command) { + if (Input.cloudRunnerCluster === 'local') { + return ''; + } + return await CloudRunnerSystem.Run(command, false, true); } } diff --git a/src/model/input-readers/git-repo.ts b/src/model/input-readers/git-repo.ts index c301f5e8..3372089e 100644 --- a/src/model/input-readers/git-repo.ts +++ b/src/model/input-readers/git-repo.ts @@ -2,9 +2,13 @@ import { assert } from 'console'; import fs from 'fs'; import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system'; import CloudRunnerLogger from '../cloud-runner/services/cloud-runner-logger'; +import Input from '../input'; export class GitRepoReader { public static async GetRemote() { + if (Input.cloudRunnerCluster === 'local') { + return ''; + } assert(fs.existsSync(`.git`)); const value = (await CloudRunnerSystem.Run(`git remote -v`, false, true)).replace(/ /g, ``); CloudRunnerLogger.log(`value ${value}`); @@ -14,6 +18,9 @@ export class GitRepoReader { } public static async GetBranch() { + if (Input.cloudRunnerCluster === 'local') { + return ''; + } assert(fs.existsSync(`.git`)); return (await CloudRunnerSystem.Run(`git branch --show-current`, false, true)) diff --git a/src/model/input-readers/github-cli.ts b/src/model/input-readers/github-cli.ts index 4e9754a2..7ad6ef7a 100644 --- a/src/model/input-readers/github-cli.ts +++ b/src/model/input-readers/github-cli.ts @@ -1,8 +1,12 @@ import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system'; import * as core from '@actions/core'; +import Input from '../input'; export class GithubCliReader { static async GetGitHubAuthToken() { + if (Input.cloudRunnerCluster === 'local') { + return ''; + } try { const authStatus = await CloudRunnerSystem.Run(`gh auth status`, true, true); if (authStatus.includes('You are not logged') || authStatus === '') { diff --git a/src/model/input-readers/test-license-reader.ts b/src/model/input-readers/test-license-reader.ts index b55ce73a..5d072743 100644 --- a/src/model/input-readers/test-license-reader.ts +++ b/src/model/input-readers/test-license-reader.ts @@ -1,8 +1,12 @@ import path from 'path'; import fs from 'fs'; import YAML from 'yaml'; +import Input from '../input'; export function ReadLicense() { + if (Input.cloudRunnerCluster === 'local') { + return ''; + } const pipelineFile = path.join(__dirname, `.github`, `workflows`, `cloud-runner-k8s-pipeline.yml`); return fs.existsSync(pipelineFile) ? YAML.parse(fs.readFileSync(pipelineFile, 'utf8')).env.UNITY_LICENSE : ''; diff --git a/src/model/input.ts b/src/model/input.ts index 36c86c55..d3bb8116 100644 --- a/src/model/input.ts +++ b/src/model/input.ts @@ -234,11 +234,11 @@ class Input { } static get cloudRunnerCpu() { - return Input.getInput('cloudRunnerCpu') || '1.0'; + return Input.getInput('cloudRunnerCpu'); } static get cloudRunnerMemory() { - return Input.getInput('cloudRunnerMemory') || '750M'; + return Input.getInput('cloudRunnerMemory'); } static get kubeConfig() {