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
pull/397/head
Frostebite 2022-05-05 00:25:17 +01:00 committed by GitHub
parent 4556fc4ff1
commit f77696efae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 469 additions and 290 deletions

View File

@ -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 }}

View File

@ -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

View File

@ -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

321
dist/index.js generated vendored
View File

@ -231,7 +231,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_1.default.githubInputEnabled && cli_1.Cli.options === undefined) {
if (!process.env.UNITY_SERIAL && input_1.default.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
@ -1200,8 +1200,8 @@ const zlib = __importStar(__nccwpck_require__(59796));
const cloud_runner_logger_1 = __importDefault(__nccwpck_require__(22855));
const __1 = __nccwpck_require__(41359);
const cloud_runner_1 = __importDefault(__nccwpck_require__(79144));
const cloud_runner_statics_1 = __nccwpck_require__(90828);
const cloud_runner_build_command_process_1 = __nccwpck_require__(71899);
const follow_log_stream_service_1 = __nccwpck_require__(64121);
class AWSTaskRunner {
static runTask(taskDef, ECS, CF, environment, buildGuid, commands) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
@ -1293,8 +1293,8 @@ class AWSTaskRunner {
const kinesis = new AWS.Kinesis();
const stream = yield AWSTaskRunner.getLogStream(kinesis, kinesisStreamName);
let iterator = yield AWSTaskRunner.getLogIterator(kinesis, stream);
const logBaseUrl = `https://${__1.Input.region}.console.aws.amazon.com/cloudwatch/home?region=${CF.config.region}#logsV2:log-groups/log-group/${taskDef.taskDefStackName}`;
cloud_runner_logger_1.default.log(`You can also see the logs at AWS Cloud Watch: ${logBaseUrl}`);
const logBaseUrl = `https://${__1.Input.region}.console.aws.amazon.com/cloudwatch/home?region=${__1.Input.region}#logsV2:log-groups/log-group/${cloud_runner_1.default.buildParameters.awsBaseStackName}-${cloud_runner_1.default.buildParameters.buildGuid}`;
cloud_runner_logger_1.default.log(`You view the log stream on AWS Cloud Watch: ${logBaseUrl}`);
let shouldReadLogs = true;
let shouldCleanup = true;
let timestamp = 0;
@ -1343,34 +1343,8 @@ class AWSTaskRunner {
const json = JSON.parse(zlib.gunzipSync(Buffer.from(records.Records[index].Data, 'base64')).toString('utf8'));
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(`---${cloud_runner_1.default.buildParameters.logId}`)) {
cloud_runner_logger_1.default.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 = `[${cloud_runner_statics_1.CloudRunnerStatics.logPrefix}] ${message}`;
if (cloud_runner_1.default.buildParameters.cloudRunnerIntegrationTests) {
output += message;
}
cloud_runner_logger_1.default.log(message);
const message = json.logEvents[logEventsIndex].message;
({ shouldReadLogs, shouldCleanup, output } = follow_log_stream_service_1.FollowLogStreamService.handleIteration(message, shouldReadLogs, shouldCleanup, output));
}
}
}
@ -1413,8 +1387,9 @@ exports.BaseStackFormation = void 0;
class BaseStackFormation {
}
exports.BaseStackFormation = BaseStackFormation;
BaseStackFormation.baseStackDecription = `Game-CI base stack`;
BaseStackFormation.formation = `AWSTemplateFormatVersion: '2010-09-09'
Description: Game-CI base stack
Description: ${BaseStackFormation.baseStackDecription}
Parameters:
EnvironmentName:
Type: String
@ -1816,11 +1791,9 @@ exports.TaskDefinitionFormation = void 0;
class TaskDefinitionFormation {
}
exports.TaskDefinitionFormation = TaskDefinitionFormation;
TaskDefinitionFormation.description = `Game CI Cloud Runner Task Stack`;
TaskDefinitionFormation.formation = `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
@ -2005,59 +1978,13 @@ const aws_sdk_1 = __importDefault(__nccwpck_require__(71786));
const cli_functions_repository_1 = __nccwpck_require__(85301);
const input_1 = __importDefault(__nccwpck_require__(91933));
const cloud_runner_logger_1 = __importDefault(__nccwpck_require__(22855));
const base_stack_formation_1 = __nccwpck_require__(29643);
class AwsCliCommands {
static awsListStacks(perResultCallback) {
var _a;
static awsListAll() {
return __awaiter(this, void 0, void 0, function* () {
process.env.AWS_REGION = input_1.default.region;
const CF = new aws_sdk_1.default.CloudFormation();
const stacks = ((_a = (yield CF.listStacks().promise()).StackSummaries) === null || _a === void 0 ? void 0 : _a.filter((_x) => _x.StackStatus !== 'DELETE_COMPLETE')) || [];
cloud_runner_logger_1.default.log(`DescribeStacksRequest ${stacks.length}`);
for (const element of stacks) {
cloud_runner_logger_1.default.log(JSON.stringify(element, undefined, 4));
cloud_runner_logger_1.default.log(`${element.StackName}`);
if (perResultCallback)
yield perResultCallback(element);
}
if (stacks === undefined) {
return;
}
});
}
static awsListTasks(perResultCallback) {
return __awaiter(this, void 0, void 0, function* () {
process.env.AWS_REGION = input_1.default.region;
cloud_runner_logger_1.default.log(`ECS Clusters`);
const ecs = new aws_sdk_1.default.ECS();
const clusters = (yield ecs.listClusters().promise()).clusterArns || [];
for (const element of clusters) {
const input = {
cluster: element,
};
const list = (yield ecs.listTasks(input).promise()).taskArns || [];
if (list.length > 0) {
const describeInput = { tasks: list, cluster: element };
const describeList = (yield ecs.describeTasks(describeInput).promise()).tasks || [];
if (describeList === []) {
continue;
}
cloud_runner_logger_1.default.log(`DescribeTasksRequest ${describeList.length}`);
for (const taskElement of describeList) {
if (taskElement === undefined) {
continue;
}
taskElement.overrides = {};
taskElement.attachments = [];
cloud_runner_logger_1.default.log(JSON.stringify(taskElement, undefined, 4));
if (taskElement.createdAt === undefined) {
cloud_runner_logger_1.default.log(`Skipping ${taskElement.taskDefinitionArn} no createdAt date`);
continue;
}
if (perResultCallback)
yield perResultCallback(taskElement, element);
}
}
}
yield AwsCliCommands.awsListStacks(undefined, true);
yield AwsCliCommands.awsListTasks();
yield AwsCliCommands.awsListLogGroups(undefined, true);
});
}
static garbageCollectAws() {
@ -2072,32 +1999,149 @@ class AwsCliCommands {
}
static garbageCollectAwsAllOlderThanOneDay() {
return __awaiter(this, void 0, void 0, function* () {
yield AwsCliCommands.cleanup(true);
yield AwsCliCommands.cleanup(true, true);
});
}
static cleanup(deleteResources = false) {
static isOlderThan1day(date) {
const ageDate = new Date(date.getTime() - Date.now());
return ageDate.getDay() > 0;
}
static awsListStacks(perResultCallback = false, verbose = false) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
process.env.AWS_REGION = input_1.default.region;
const CF = new aws_sdk_1.default.CloudFormation();
const stacks = ((_a = (yield CF.listStacks().promise()).StackSummaries) === null || _a === void 0 ? void 0 : _a.filter((_x) => _x.StackStatus !== 'DELETE_COMPLETE')) || [];
cloud_runner_logger_1.default.log(`Stacks ${stacks.length}`);
for (const element of stacks) {
const ageDate = new Date(element.CreationTime.getTime() - Date.now());
if (verbose)
cloud_runner_logger_1.default.log(`Task Stack ${element.StackName} - Age D${ageDate.getDay()} H${ageDate.getHours()} M${ageDate.getMinutes()}`);
if (perResultCallback)
yield perResultCallback(element);
}
const baseStacks = ((_b = (yield CF.listStacks().promise()).StackSummaries) === null || _b === void 0 ? void 0 : _b.filter((_x) => _x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription === base_stack_formation_1.BaseStackFormation.baseStackDecription)) || [];
cloud_runner_logger_1.default.log(`Base Stacks ${baseStacks.length}`);
for (const element of baseStacks) {
const ageDate = new Date(element.CreationTime.getTime() - Date.now());
if (verbose)
cloud_runner_logger_1.default.log(`Base Stack ${element.StackName} - Age D${ageDate.getHours()} H${ageDate.getHours()} M${ageDate.getMinutes()}`);
if (perResultCallback)
yield perResultCallback(element);
}
if (stacks === undefined) {
return;
}
});
}
static awsListTasks(perResultCallback = false) {
return __awaiter(this, void 0, void 0, function* () {
process.env.AWS_REGION = input_1.default.region;
const ecs = new aws_sdk_1.default.ECS();
const clusters = (yield ecs.listClusters().promise()).clusterArns || [];
cloud_runner_logger_1.default.log(`Clusters ${clusters.length}`);
for (const element of clusters) {
const input = {
cluster: element,
};
const list = (yield ecs.listTasks(input).promise()).taskArns || [];
if (list.length > 0) {
const describeInput = { tasks: list, cluster: element };
const describeList = (yield ecs.describeTasks(describeInput).promise()).tasks || [];
if (describeList === []) {
continue;
}
cloud_runner_logger_1.default.log(`Tasks ${describeList.length}`);
for (const taskElement of describeList) {
if (taskElement === undefined) {
continue;
}
taskElement.overrides = {};
taskElement.attachments = [];
if (taskElement.createdAt === undefined) {
cloud_runner_logger_1.default.log(`Skipping ${taskElement.taskDefinitionArn} no createdAt date`);
continue;
}
if (perResultCallback)
yield perResultCallback(taskElement, element);
}
}
}
});
}
static awsListLogGroups(perResultCallback = false, verbose = false) {
return __awaiter(this, void 0, void 0, function* () {
process.env.AWS_REGION = input_1.default.region;
const ecs = new aws_sdk_1.default.CloudWatchLogs();
let logStreamInput = {
/* logGroupNamePrefix: 'game-ci' */
};
let logGroupsDescribe = yield ecs.describeLogGroups(logStreamInput).promise();
const logGroups = logGroupsDescribe.logGroups || [];
while (logGroupsDescribe.nextToken) {
logStreamInput = { /* logGroupNamePrefix: 'game-ci',*/ nextToken: logGroupsDescribe.nextToken };
logGroupsDescribe = yield ecs.describeLogGroups(logStreamInput).promise();
logGroups.push(...((logGroupsDescribe === null || logGroupsDescribe === void 0 ? void 0 : logGroupsDescribe.logGroups) || []));
}
cloud_runner_logger_1.default.log(`Log Groups ${logGroups.length}`);
for (const element of logGroups) {
if (element.creationTime === undefined) {
cloud_runner_logger_1.default.log(`Skipping ${element.logGroupName} no createdAt date`);
continue;
}
const ageDate = new Date(new Date(element.creationTime).getTime() - Date.now());
if (verbose)
cloud_runner_logger_1.default.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)
yield perResultCallback(element, element);
}
});
}
static cleanup(deleteResources = false, OneDayOlderOnly = false) {
return __awaiter(this, void 0, void 0, function* () {
process.env.AWS_REGION = input_1.default.region;
const CF = new aws_sdk_1.default.CloudFormation();
const ecs = new aws_sdk_1.default.ECS();
const cwl = new aws_sdk_1.default.CloudWatchLogs();
yield AwsCliCommands.awsListStacks((element) => __awaiter(this, void 0, void 0, function* () {
if (deleteResources) {
if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(element.CreationTime))) {
if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') {
cloud_runner_logger_1.default.log(`Skipping ${element.StackName} ignore list`);
return;
}
cloud_runner_logger_1.default.log(`Deleting ${element.logGroupName}`);
const deleteStackInput = { StackName: element.StackName };
yield CF.deleteStack(deleteStackInput).promise();
}
}));
yield AwsCliCommands.awsListTasks((taskElement, element) => __awaiter(this, void 0, void 0, function* () {
if (deleteResources) {
var _a;
if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(taskElement.CreatedAt))) {
cloud_runner_logger_1.default.log(`Stopping task ${(_a = taskElement.containers) === null || _a === void 0 ? void 0 : _a[0].name}`);
yield ecs.stopTask({ task: taskElement.taskArn || '', cluster: element }).promise();
}
}));
yield AwsCliCommands.awsListLogGroups((element) => __awaiter(this, void 0, void 0, function* () {
if (deleteResources && (!OneDayOlderOnly || AwsCliCommands.isOlderThan1day(new Date(element.createdAt)))) {
cloud_runner_logger_1.default.log(`Deleting ${element.logGroupName}`);
yield cwl.deleteLogGroup({ logGroupName: element.logGroupName || '' }).promise();
}
}));
});
}
}
__decorate([
cli_functions_repository_1.CliFunction(`aws-list-all`, `List all resources`)
], AwsCliCommands, "awsListAll", null);
__decorate([
cli_functions_repository_1.CliFunction(`aws-garbage-collect`, `garbage collect aws resources not in use !WIP!`)
], AwsCliCommands, "garbageCollectAws", null);
__decorate([
cli_functions_repository_1.CliFunction(`aws-garbage-collect-all`, `garbage collect aws resources regardless of whether they are in use`)
], AwsCliCommands, "garbageCollectAwsAll", null);
__decorate([
cli_functions_repository_1.CliFunction(`aws-garbage-collect-all-1d-older`, `garbage collect aws resources created more than 1d ago (ignore if they are in use)`)
], AwsCliCommands, "garbageCollectAwsAllOlderThanOneDay", null);
__decorate([
cli_functions_repository_1.CliFunction(`aws-list-stacks`, `List stacks`)
], AwsCliCommands, "awsListStacks", null);
@ -2105,14 +2149,8 @@ __decorate([
cli_functions_repository_1.CliFunction(`aws-list-tasks`, `List tasks`)
], AwsCliCommands, "awsListTasks", null);
__decorate([
cli_functions_repository_1.CliFunction(`aws-garbage-collect`, `garbage collect aws`)
], AwsCliCommands, "garbageCollectAws", null);
__decorate([
cli_functions_repository_1.CliFunction(`aws-garbage-collect-all`, `garbage collect aws`)
], AwsCliCommands, "garbageCollectAwsAll", null);
__decorate([
cli_functions_repository_1.CliFunction(`aws-garbage-collect-all-1d-older`, `garbage collect aws`)
], AwsCliCommands, "garbageCollectAwsAllOlderThanOneDay", null);
cli_functions_repository_1.CliFunction(`aws-list-log-groups`, `List tasks`)
], AwsCliCommands, "awsListLogGroups", null);
exports.AwsCliCommands = AwsCliCommands;
@ -2347,7 +2385,7 @@ class Kubernetes {
try {
yield kubernetes_task_runner_1.default.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
cloud_runner_logger_1.default.log('Pod running, streaming logs');
output = yield kubernetes_task_runner_1.default.runTask(this.kubeConfig, this.kubeClient, this.jobName, this.podName, 'main', this.namespace, cloud_runner_logger_1.default.log);
output = yield kubernetes_task_runner_1.default.runTask(this.kubeConfig, this.kubeClient, this.jobName, this.podName, 'main', this.namespace);
break;
}
catch (error) {
@ -2882,22 +2920,21 @@ const cloud_runner_logger_1 = __importDefault(__nccwpck_require__(22855));
const core = __importStar(__nccwpck_require__(42186));
const cloud_runner_statics_1 = __nccwpck_require__(90828);
const async_wait_until_1 = __importDefault(__nccwpck_require__(41299));
const cloud_runner_1 = __importDefault(__nccwpck_require__(79144));
const follow_log_stream_service_1 = __nccwpck_require__(64121);
class KubernetesTaskRunner {
static runTask(kubeConfig, kubeClient, jobName, podName, containerName, namespace, logCallback) {
static runTask(kubeConfig, kubeClient, jobName, podName, containerName, namespace) {
return __awaiter(this, void 0, void 0, function* () {
cloud_runner_logger_1.default.log(`Streaming logs from pod: ${podName} container: ${containerName} namespace: ${namespace}`);
const stream = new stream_1.Writable();
let output = '';
let didStreamAnyLogs = false;
let shouldReadLogs = true;
let shouldCleanup = true;
stream._write = (chunk, encoding, next) => {
didStreamAnyLogs = true;
let message = chunk.toString().trimRight(`\n`);
message = `[${cloud_runner_statics_1.CloudRunnerStatics.logPrefix}] ${message}`;
if (cloud_runner_1.default.buildParameters.cloudRunnerIntegrationTests) {
output += message;
}
logCallback(message);
({ shouldReadLogs, shouldCleanup, output } = follow_log_stream_service_1.FollowLogStreamService.handleIteration(message, shouldReadLogs, shouldCleanup, output));
next();
};
const logOptions = {
@ -3869,6 +3906,76 @@ class DependencyOverrideService {
exports["default"] = DependencyOverrideService;
/***/ }),
/***/ 64121:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.FollowLogStreamService = void 0;
const cloud_runner_logger_1 = __importDefault(__nccwpck_require__(22855));
const core = __importStar(__nccwpck_require__(42186));
const cloud_runner_1 = __importDefault(__nccwpck_require__(79144));
const cloud_runner_statics_1 = __nccwpck_require__(90828);
class FollowLogStreamService {
static handleIteration(message, shouldReadLogs, shouldCleanup, output) {
if (message.includes(`---${cloud_runner_1.default.buildParameters.logId}`)) {
cloud_runner_logger_1.default.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 (cloud_runner_1.default.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 = `[${cloud_runner_statics_1.CloudRunnerStatics.logPrefix}] ${message}`;
if (cloud_runner_1.default.buildParameters.cloudRunnerIntegrationTests) {
output += message;
}
cloud_runner_logger_1.default.log(message);
return { shouldReadLogs, shouldCleanup, output };
}
}
exports.FollowLogStreamService = FollowLogStreamService;
/***/ }),
/***/ 8915:

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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,
));
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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();
}
});
}
}

View File

@ -119,7 +119,6 @@ class Kubernetes implements ProviderInterface {
this.podName,
'main',
this.namespace,
CloudRunnerLogger.log,
);
break;
} catch (error: any) {

View File

@ -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 = {

View File

@ -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 };
}
}