mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-02-05 08:59:09 +08:00
Compare commits
12 Commits
v2.0-alpha
...
v2.0-alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c317d144c3 | ||
|
|
3ebdb0e678 | ||
|
|
7d9f968d70 | ||
|
|
70081d231f | ||
|
|
36364461d5 | ||
|
|
b66dffbf92 | ||
|
|
2fc01a1db4 | ||
|
|
8c1a159dd0 | ||
|
|
497f2f7b5f | ||
|
|
71ca7bdbfc | ||
|
|
c96b8cf443 | ||
|
|
9fff362775 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: game-ci
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
otechie: # Replace with a single Otechie username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an improvement, or a new feature
|
about: Suggest an improvement, or a new feature
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/aws-tests.yml
vendored
6
.github/workflows/aws-tests.yml
vendored
@@ -1,10 +1,10 @@
|
|||||||
name: AWS
|
name: AWS
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push: { branches: [aws, aws-ts-clean] }
|
push: { branches: [aws, remote-builder/refactor] }
|
||||||
|
|
||||||
env:
|
env:
|
||||||
AWS_REGION: "eu-west-1"
|
AWS_REGION: 'eu-west-1'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
buildForAllPlatforms:
|
buildForAllPlatforms:
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
projectPath:
|
projectPath:
|
||||||
- test-project
|
- test-project
|
||||||
unityVersion:
|
unityVersion:
|
||||||
# - 2019.2.11f1
|
# - 2019.2.11f1
|
||||||
- 2019.3.15f1
|
- 2019.3.15f1
|
||||||
targetPlatform:
|
targetPlatform:
|
||||||
#- StandaloneOSX # Build a macOS standalone (Intel 64-bit).
|
#- StandaloneOSX # Build a macOS standalone (Intel 64-bit).
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -41,6 +41,14 @@ To help improve the documentation, please find the docs [repository](https://git
|
|||||||
|
|
||||||
To contribute to Unity Builder, kindly read the [contribution guide](./CONTRIBUTING.md).
|
To contribute to Unity Builder, kindly read the [contribution guide](./CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## Support us
|
||||||
|
|
||||||
|
GameCI is free for everyone forever.
|
||||||
|
|
||||||
|
You can support us at [OpenCollective](https://opencollective.com/game-ci).
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
[MIT](./LICENSE)
|
This repository is [MIT](./LICENSE) licensed.
|
||||||
|
|
||||||
|
This includes all contributions from the community.
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ inputs:
|
|||||||
|
|
||||||
Parameters must start with a hyphen (-) and may be followed by a value (without hyphen).
|
Parameters must start with a hyphen (-) and may be followed by a value (without hyphen).
|
||||||
Parameters without a value will be considered booleans (with a value of true).
|
Parameters without a value will be considered booleans (with a value of true).
|
||||||
|
sshAgent:
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
description: 'SSH Agent path to forward to the container'
|
||||||
chownFilesTo:
|
chownFilesTo:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
|
|||||||
111
dist/cloud-formations/task-def-formation.yml
vendored
111
dist/cloud-formations/task-def-formation.yml
vendored
@@ -51,36 +51,7 @@ Parameters:
|
|||||||
EFSMountDirectory:
|
EFSMountDirectory:
|
||||||
Type: String
|
Type: String
|
||||||
Default: '/efsdata'
|
Default: '/efsdata'
|
||||||
GithubToken:
|
# template secrets p1 - input
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
UnityLicense:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
UnityEmail:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
UnityPassword:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
UnitySerial:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
AndroidKeystoreBase64:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
AndroidKeystorePass:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
AndroidKeyAliasPass:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
AWSAccessKeyID:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
AWSSecretAccessKey:
|
|
||||||
Type: String
|
|
||||||
Default: '0'
|
|
||||||
Mappings:
|
Mappings:
|
||||||
SubnetConfig:
|
SubnetConfig:
|
||||||
VPC:
|
VPC:
|
||||||
@@ -128,64 +99,8 @@ Resources:
|
|||||||
'AWS::CloudFormation::Designer':
|
'AWS::CloudFormation::Designer':
|
||||||
id: c6f18447-b879-4696-8873-f981b2cedd2b
|
id: c6f18447-b879-4696-8873-f981b2cedd2b
|
||||||
|
|
||||||
GithubTokenSecret:
|
# template secrets p2 - secret
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'GithubToken', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref GithubToken
|
|
||||||
|
|
||||||
UnityLicenseSecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'UnityLicense', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref UnityLicense
|
|
||||||
|
|
||||||
UnityEmailSecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'UnityEmail', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref UnityEmail
|
|
||||||
|
|
||||||
UnityPasswordSecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'UnityPassword', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref UnityPassword
|
|
||||||
|
|
||||||
UnitySerialSecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'UnitySerial', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref UnitySerial
|
|
||||||
|
|
||||||
AndroidKeystoreBase64Secret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'AndroidKeystoreBase64', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref AndroidKeystoreBase64
|
|
||||||
|
|
||||||
AndroidKeystorePassSecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'AndroidKeystorePass', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref AndroidKeystorePass
|
|
||||||
|
|
||||||
AndroidKeyAliasPassSecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'AndroidKeyAliasPass', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref AndroidKeyAliasPass
|
|
||||||
AWSAccessKeyIDSecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'AWSAccessKeyID', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref AWSAccessKeyID
|
|
||||||
AWSSecretAccessKeySecret:
|
|
||||||
Type: AWS::SecretsManager::Secret
|
|
||||||
Properties:
|
|
||||||
Name: !Join [ "", [ 'AWSSecretAccessKey', !Ref BUILDID ] ]
|
|
||||||
SecretString: !Ref AWSSecretAccessKey
|
|
||||||
|
|
||||||
TaskDefinition:
|
TaskDefinition:
|
||||||
Type: 'AWS::ECS::TaskDefinition'
|
Type: 'AWS::ECS::TaskDefinition'
|
||||||
Properties:
|
Properties:
|
||||||
@@ -225,29 +140,13 @@ Resources:
|
|||||||
Environment:
|
Environment:
|
||||||
- Name: ALLOW_EMPTY_PASSWORD
|
- Name: ALLOW_EMPTY_PASSWORD
|
||||||
Value: 'yes'
|
Value: 'yes'
|
||||||
|
# template - env vars
|
||||||
MountPoints:
|
MountPoints:
|
||||||
- SourceVolume: efs-data
|
- SourceVolume: efs-data
|
||||||
ContainerPath: !Ref EFSMountDirectory
|
ContainerPath: !Ref EFSMountDirectory
|
||||||
ReadOnly: false
|
ReadOnly: false
|
||||||
Secrets:
|
Secrets:
|
||||||
- Name: 'GITHUB_TOKEN'
|
# template secrets p3 - container def
|
||||||
ValueFrom: !Ref GithubTokenSecret
|
|
||||||
- Name: 'UNITY_LICENSE'
|
|
||||||
ValueFrom: !Ref UnityLicenseSecret
|
|
||||||
- Name: 'UNITY_EMAIL'
|
|
||||||
ValueFrom: !Ref UnityEmailSecret
|
|
||||||
- Name: 'UNITY_PASSWORD'
|
|
||||||
ValueFrom: !Ref UnityPasswordSecret
|
|
||||||
- Name: 'UNITY_SERIAL'
|
|
||||||
ValueFrom: !Ref UnitySerialSecret
|
|
||||||
- Name: 'ANDROID_KEYSTORE_BASE64'
|
|
||||||
ValueFrom: !Ref AndroidKeystoreBase64Secret
|
|
||||||
- Name: 'ANDROID_KEYSTORE_PASS'
|
|
||||||
ValueFrom: !Ref AndroidKeystorePassSecret
|
|
||||||
- Name: 'AWS_ACCESS_KEY_ID'
|
|
||||||
ValueFrom: !Ref AWSAccessKeyIDSecret
|
|
||||||
- Name: 'AWS_SECRET_ACCESS_KEY'
|
|
||||||
ValueFrom: !Ref AWSSecretAccessKeySecret
|
|
||||||
LogConfiguration:
|
LogConfiguration:
|
||||||
LogDriver: awslogs
|
LogDriver: awslogs
|
||||||
Options:
|
Options:
|
||||||
|
|||||||
13
dist/entrypoint.sh
vendored
13
dist/entrypoint.sh
vendored
@@ -1,5 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create directory for license activation
|
||||||
|
#
|
||||||
|
|
||||||
|
ACTIVATE_LICENSE_PATH="$GITHUB_WORKSPACE/_activate-license"
|
||||||
|
mkdir -p "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Run steps
|
# Run steps
|
||||||
#
|
#
|
||||||
@@ -8,6 +15,12 @@ source /steps/activate.sh
|
|||||||
source /steps/build.sh
|
source /steps/build.sh
|
||||||
source /steps/return_license.sh
|
source /steps/return_license.sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Remove license activation directory
|
||||||
|
#
|
||||||
|
|
||||||
|
rm -r "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Instructions for debugging
|
# Instructions for debugging
|
||||||
#
|
#
|
||||||
|
|||||||
1391
dist/index.js
generated
vendored
1391
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load Diff
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
10
dist/steps/activate.sh
vendored
10
dist/steps/activate.sh
vendored
@@ -1,5 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Run in ACTIVATE_LICENSE_PATH directory
|
||||||
|
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
||||||
|
pushd "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
if [[ -n "$UNITY_LICENSE" ]] || [[ -n "$UNITY_LICENSE_FILE" ]]; then
|
if [[ -n "$UNITY_LICENSE" ]] || [[ -n "$UNITY_LICENSE_FILE" ]]; then
|
||||||
#
|
#
|
||||||
# PERSONAL LICENSE MODE
|
# PERSONAL LICENSE MODE
|
||||||
@@ -25,7 +29,6 @@ if [[ -n "$UNITY_LICENSE" ]] || [[ -n "$UNITY_LICENSE_FILE" ]]; then
|
|||||||
|
|
||||||
# Activate license
|
# Activate license
|
||||||
ACTIVATION_OUTPUT=$(unity-editor \
|
ACTIVATION_OUTPUT=$(unity-editor \
|
||||||
-nographics \
|
|
||||||
-logFile /dev/stdout \
|
-logFile /dev/stdout \
|
||||||
-quit \
|
-quit \
|
||||||
-manualLicenseFile $FILE_PATH)
|
-manualLicenseFile $FILE_PATH)
|
||||||
@@ -62,8 +65,6 @@ elif [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
|
|||||||
|
|
||||||
# Activate license
|
# Activate license
|
||||||
unity-editor \
|
unity-editor \
|
||||||
-batchmode \
|
|
||||||
-nographics \
|
|
||||||
-logFile /dev/stdout \
|
-logFile /dev/stdout \
|
||||||
-quit \
|
-quit \
|
||||||
-serial "$UNITY_SERIAL" \
|
-serial "$UNITY_SERIAL" \
|
||||||
@@ -101,3 +102,6 @@ else
|
|||||||
echo "Exit code was: $UNITY_EXIT_CODE"
|
echo "Exit code was: $UNITY_EXIT_CODE"
|
||||||
exit $UNITY_EXIT_CODE
|
exit $UNITY_EXIT_CODE
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Return to previous working directory
|
||||||
|
popd
|
||||||
|
|||||||
1
dist/steps/build.sh
vendored
1
dist/steps/build.sh
vendored
@@ -116,7 +116,6 @@ echo ""
|
|||||||
# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html
|
# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html
|
||||||
|
|
||||||
unity-editor \
|
unity-editor \
|
||||||
-nographics \
|
|
||||||
-logfile /dev/stdout \
|
-logfile /dev/stdout \
|
||||||
-quit \
|
-quit \
|
||||||
-customBuildName "$BUILD_NAME" \
|
-customBuildName "$BUILD_NAME" \
|
||||||
|
|||||||
8
dist/steps/return_license.sh
vendored
8
dist/steps/return_license.sh
vendored
@@ -1,5 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Run in ACTIVATE_LICENSE_PATH directory
|
||||||
|
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
||||||
|
pushd "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
if [[ -n "$UNITY_SERIAL" ]]; then
|
if [[ -n "$UNITY_SERIAL" ]]; then
|
||||||
#
|
#
|
||||||
# PROFESSIONAL (SERIAL) LICENSE MODE
|
# PROFESSIONAL (SERIAL) LICENSE MODE
|
||||||
@@ -7,8 +11,10 @@ if [[ -n "$UNITY_SERIAL" ]]; then
|
|||||||
# This will return the license that is currently in use.
|
# This will return the license that is currently in use.
|
||||||
#
|
#
|
||||||
unity-editor \
|
unity-editor \
|
||||||
-nographics \
|
|
||||||
-logFile /dev/stdout \
|
-logFile /dev/stdout \
|
||||||
-quit \
|
-quit \
|
||||||
-returnlicense
|
-returnlicense
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Return to previous working directory
|
||||||
|
popd
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import { Action, BuildParameters, Cache, Docker, ImageTag, Kubernetes, Output, AWS } from './model';
|
import { Action, BuildParameters, Cache, Docker, ImageTag, Kubernetes, Output, RemoteBuilder } from './model';
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
@@ -20,7 +20,7 @@ async function run() {
|
|||||||
|
|
||||||
case 'aws':
|
case 'aws':
|
||||||
core.info('Building with AWS');
|
core.info('Building with AWS');
|
||||||
await AWS.runBuildJob(buildParameters, baseImage);
|
await RemoteBuilder.build(buildParameters, baseImage);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// default and local case
|
// default and local case
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const mockGetFromUser = jest.fn().mockResolvedValue({
|
|||||||
buildMethod: undefined,
|
buildMethod: undefined,
|
||||||
buildVersion: '1.3.37',
|
buildVersion: '1.3.37',
|
||||||
customParameters: '',
|
customParameters: '',
|
||||||
|
sshAgent: '',
|
||||||
chownFilesTo: '',
|
chownFilesTo: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
611
src/model/aws.ts
611
src/model/aws.ts
@@ -1,611 +0,0 @@
|
|||||||
import * as SDK from 'aws-sdk';
|
|
||||||
import { customAlphabet } from 'nanoid';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import * as zlib from 'zlib';
|
|
||||||
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
||||||
const repositoryDirectoryName = 'repo';
|
|
||||||
const efsDirectoryName = 'data';
|
|
||||||
const cacheDirectoryName = 'cache';
|
|
||||||
|
|
||||||
class AWS {
|
|
||||||
static async runBuildJob(buildParameters, baseImage) {
|
|
||||||
try {
|
|
||||||
const nanoid = customAlphabet(alphabet, 4);
|
|
||||||
const buildUid = `${process.env.GITHUB_RUN_NUMBER}-${buildParameters.platform
|
|
||||||
.replace('Standalone', '')
|
|
||||||
.replace('standalone', '')}-${nanoid()}`;
|
|
||||||
const branchName = process.env.GITHUB_REF?.split('/').reverse()[0];
|
|
||||||
|
|
||||||
core.info('Starting part 1/4 (clone from github and restore cache)');
|
|
||||||
await this.run(
|
|
||||||
buildUid,
|
|
||||||
buildParameters.awsStackName,
|
|
||||||
'alpine/git',
|
|
||||||
['/bin/sh'],
|
|
||||||
[
|
|
||||||
'-c',
|
|
||||||
`apk update;
|
|
||||||
apk add unzip;
|
|
||||||
apk add git-lfs;
|
|
||||||
apk add jq;
|
|
||||||
# Get source repo for project to be built and game-ci repo for utilties
|
|
||||||
git clone https://${buildParameters.githubToken}@github.com/${process.env.GITHUB_REPOSITORY}.git ${buildUid}/${repositoryDirectoryName} -q
|
|
||||||
git clone https://${buildParameters.githubToken}@github.com/game-ci/unity-builder.git ${buildUid}/builder -q
|
|
||||||
cd /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/
|
|
||||||
git checkout $GITHUB_SHA
|
|
||||||
cd /${efsDirectoryName}/
|
|
||||||
# Look for usable cache
|
|
||||||
if [ ! -d ${cacheDirectoryName} ]; then
|
|
||||||
mkdir ${cacheDirectoryName}
|
|
||||||
fi
|
|
||||||
cd ${cacheDirectoryName}
|
|
||||||
if [ ! -d "${branchName}" ]; then
|
|
||||||
mkdir "${branchName}"
|
|
||||||
fi
|
|
||||||
cd "${branchName}"
|
|
||||||
echo " "
|
|
||||||
echo "Cached Libraries for ${branchName} from previous builds:"
|
|
||||||
ls
|
|
||||||
echo " "
|
|
||||||
libDir="/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}/Library"
|
|
||||||
if [ -d "$libDir" ]; then
|
|
||||||
rm -r "$libDir"
|
|
||||||
echo "Setup .gitignore to ignore Library folder and remove it from builds"
|
|
||||||
fi
|
|
||||||
echo 'Checking cache'
|
|
||||||
# Restore cache
|
|
||||||
latest=$(ls -t | head -1)
|
|
||||||
if [ ! -z "$latest" ]; then
|
|
||||||
echo "Library cache exists from build $latest from ${branchName}"
|
|
||||||
echo 'Creating empty Library folder for cache'
|
|
||||||
mkdir "$libDir"
|
|
||||||
unzip -q $latest -d '/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}/Library/.'
|
|
||||||
else
|
|
||||||
echo 'Cache does not exist'
|
|
||||||
fi
|
|
||||||
# Print out important directories
|
|
||||||
echo ' '
|
|
||||||
echo 'Repo:'
|
|
||||||
ls /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/
|
|
||||||
echo ' '
|
|
||||||
echo 'Project:'
|
|
||||||
ls /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}
|
|
||||||
echo ' '
|
|
||||||
echo 'Library:'
|
|
||||||
ls /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}/Library/
|
|
||||||
echo ' '
|
|
||||||
`,
|
|
||||||
],
|
|
||||||
`/${efsDirectoryName}`,
|
|
||||||
`/${efsDirectoryName}/`,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'GITHUB_SHA',
|
|
||||||
value: process.env.GITHUB_SHA,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
ParameterKey: 'GithubToken',
|
|
||||||
ParameterValue: buildParameters.githubToken,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
core.info('Starting part 2/4 (build unity project)');
|
|
||||||
await this.run(
|
|
||||||
buildUid,
|
|
||||||
buildParameters.awsStackName,
|
|
||||||
baseImage.toString(),
|
|
||||||
['/bin/sh'],
|
|
||||||
[
|
|
||||||
'-c',
|
|
||||||
`
|
|
||||||
cp -r /${efsDirectoryName}/${buildUid}/builder/dist/default-build-script/ /UnityBuilderAction;
|
|
||||||
cp -r /${efsDirectoryName}/${buildUid}/builder/dist/entrypoint.sh /entrypoint.sh;
|
|
||||||
cp -r /${efsDirectoryName}/${buildUid}/builder/dist/steps/ /steps;
|
|
||||||
chmod -R +x /entrypoint.sh;
|
|
||||||
chmod -R +x /steps;
|
|
||||||
/entrypoint.sh;
|
|
||||||
`,
|
|
||||||
],
|
|
||||||
`/${efsDirectoryName}`,
|
|
||||||
`/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/`,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'ContainerMemory',
|
|
||||||
value: buildParameters.remoteBuildMemory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ContainerCpu',
|
|
||||||
value: buildParameters.remoteBuildCpu,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'GITHUB_WORKSPACE',
|
|
||||||
value: `/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'PROJECT_PATH',
|
|
||||||
value: buildParameters.projectPath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'BUILD_PATH',
|
|
||||||
value: buildParameters.buildPath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'BUILD_FILE',
|
|
||||||
value: buildParameters.buildFile,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'BUILD_NAME',
|
|
||||||
value: buildParameters.buildName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'BUILD_METHOD',
|
|
||||||
value: buildParameters.buildMethod,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CUSTOM_PARAMETERS',
|
|
||||||
value: buildParameters.customParameters,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CHOWN_FILES_TO',
|
|
||||||
value: buildParameters.chownFilesTo,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'BUILD_TARGET',
|
|
||||||
value: buildParameters.platform,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ANDROID_VERSION_CODE',
|
|
||||||
value: buildParameters.androidVersionCode.toString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ANDROID_KEYSTORE_NAME',
|
|
||||||
value: buildParameters.androidKeystoreName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ANDROID_KEYALIAS_NAME',
|
|
||||||
value: buildParameters.androidKeyaliasName,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
ParameterKey: 'GithubToken',
|
|
||||||
ParameterValue: buildParameters.githubToken,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'UnityLicense',
|
|
||||||
ParameterValue: process.env.UNITY_LICENSE ? process.env.UNITY_LICENSE : '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'UnityEmail',
|
|
||||||
ParameterValue: process.env.UNITY_EMAIL ? process.env.UNITY_EMAIL : '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'UnityPassword',
|
|
||||||
ParameterValue: process.env.UNITY_PASSWORD ? process.env.UNITY_PASSWORD : '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'UnitySerial',
|
|
||||||
ParameterValue: process.env.UNITY_SERIAL ? process.env.UNITY_SERIAL : '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'AndroidKeystoreBase64',
|
|
||||||
ParameterValue: buildParameters.androidKeystoreBase64 ? buildParameters.androidKeystoreBase64 : '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'AndroidKeystorePass',
|
|
||||||
ParameterValue: buildParameters.androidKeystorePass ? buildParameters.androidKeystorePass : '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'AndroidKeyAliasPass',
|
|
||||||
ParameterValue: buildParameters.androidKeyaliasPass ? buildParameters.androidKeyaliasPass : '0',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
core.info('Starting part 3/4 (zip unity build and Library for caching)');
|
|
||||||
// Cleanup
|
|
||||||
await this.run(
|
|
||||||
buildUid,
|
|
||||||
buildParameters.awsStackName,
|
|
||||||
'alpine',
|
|
||||||
['/bin/sh'],
|
|
||||||
[
|
|
||||||
'-c',
|
|
||||||
`
|
|
||||||
apk update
|
|
||||||
apk add zip
|
|
||||||
cd Library
|
|
||||||
zip -q -r lib-${buildUid}.zip .*
|
|
||||||
mv lib-${buildUid}.zip /${efsDirectoryName}/${cacheDirectoryName}/${branchName}/lib-${buildUid}.zip
|
|
||||||
cd ../../
|
|
||||||
zip -q -r build-${buildUid}.zip ${buildParameters.buildPath}/*
|
|
||||||
mv build-${buildUid}.zip /${efsDirectoryName}/${buildUid}/build-${buildUid}.zip
|
|
||||||
`,
|
|
||||||
],
|
|
||||||
`/${efsDirectoryName}`,
|
|
||||||
`/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}`,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'GITHUB_SHA',
|
|
||||||
value: process.env.GITHUB_SHA,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
ParameterKey: 'GithubToken',
|
|
||||||
ParameterValue: buildParameters.githubToken,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
core.info('Starting part 4/4 (upload build to s3)');
|
|
||||||
await this.run(
|
|
||||||
buildUid,
|
|
||||||
buildParameters.awsStackName,
|
|
||||||
'amazon/aws-cli',
|
|
||||||
['/bin/sh'],
|
|
||||||
[
|
|
||||||
'-c',
|
|
||||||
`
|
|
||||||
aws s3 cp ${buildUid}/build-${buildUid}.zip s3://game-ci-storage/
|
|
||||||
# no need to upload Library cache for now
|
|
||||||
# aws s3 cp /${efsDirectoryName}/${cacheDirectoryName}/${branchName}/lib-${buildUid}.zip s3://game-ci-storage/
|
|
||||||
rm -r ${buildUid}
|
|
||||||
`,
|
|
||||||
],
|
|
||||||
`/${efsDirectoryName}`,
|
|
||||||
`/${efsDirectoryName}/`,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'GITHUB_SHA',
|
|
||||||
value: process.env.GITHUB_SHA,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'AWS_DEFAULT_REGION',
|
|
||||||
value: process.env.AWS_DEFAULT_REGION,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
ParameterKey: 'GithubToken',
|
|
||||||
ParameterValue: buildParameters.githubToken,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'AWSAccessKeyID',
|
|
||||||
ParameterValue: process.env.AWS_ACCESS_KEY_ID,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'AWSSecretAccessKey',
|
|
||||||
ParameterValue: process.env.AWS_SECRET_ACCESS_KEY,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(error);
|
|
||||||
core.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async run(
|
|
||||||
buildUid: string,
|
|
||||||
stackName: string,
|
|
||||||
image: string,
|
|
||||||
entrypoint: string[],
|
|
||||||
commands,
|
|
||||||
mountdir,
|
|
||||||
workingdir,
|
|
||||||
environment,
|
|
||||||
secrets,
|
|
||||||
) {
|
|
||||||
const ECS = new SDK.ECS();
|
|
||||||
const CF = new SDK.CloudFormation();
|
|
||||||
|
|
||||||
const taskDef = await this.setupCloudFormations(
|
|
||||||
CF,
|
|
||||||
buildUid,
|
|
||||||
stackName,
|
|
||||||
image,
|
|
||||||
entrypoint,
|
|
||||||
commands,
|
|
||||||
mountdir,
|
|
||||||
workingdir,
|
|
||||||
secrets,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.runTask(taskDef, ECS, CF, environment, buildUid);
|
|
||||||
|
|
||||||
await this.cleanupResources(CF, taskDef);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async setupCloudFormations(
|
|
||||||
CF,
|
|
||||||
buildUid: string,
|
|
||||||
stackName: string,
|
|
||||||
image: string,
|
|
||||||
entrypoint: string[],
|
|
||||||
commands,
|
|
||||||
mountdir,
|
|
||||||
workingdir,
|
|
||||||
secrets,
|
|
||||||
) {
|
|
||||||
const logid = customAlphabet(alphabet, 9)();
|
|
||||||
commands[1] += `
|
|
||||||
echo "${logid}"
|
|
||||||
`;
|
|
||||||
const taskDefStackName = `${stackName}-${buildUid}`;
|
|
||||||
const taskDefCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/task-def-formation.yml`, 'utf8');
|
|
||||||
await CF.createStack({
|
|
||||||
StackName: taskDefStackName,
|
|
||||||
TemplateBody: taskDefCloudFormation,
|
|
||||||
Parameters: [
|
|
||||||
{
|
|
||||||
ParameterKey: 'ImageUrl',
|
|
||||||
ParameterValue: image,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'ServiceName',
|
|
||||||
ParameterValue: taskDefStackName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'Command',
|
|
||||||
ParameterValue: commands.join(','),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'EntryPoint',
|
|
||||||
ParameterValue: entrypoint.join(','),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'WorkingDirectory',
|
|
||||||
ParameterValue: workingdir,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'EFSMountDirectory',
|
|
||||||
ParameterValue: mountdir,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'BUILDID',
|
|
||||||
ParameterValue: buildUid,
|
|
||||||
},
|
|
||||||
...secrets,
|
|
||||||
],
|
|
||||||
}).promise();
|
|
||||||
core.info('Creating worker cluster...');
|
|
||||||
|
|
||||||
const cleanupTaskDefStackName = `${taskDefStackName}-cleanup`;
|
|
||||||
const cleanupCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/cloudformation-stack-ttl.yml`, 'utf8');
|
|
||||||
await CF.createStack({
|
|
||||||
StackName: cleanupTaskDefStackName,
|
|
||||||
TemplateBody: cleanupCloudFormation,
|
|
||||||
Capabilities: ['CAPABILITY_IAM'],
|
|
||||||
Parameters: [
|
|
||||||
{
|
|
||||||
ParameterKey: 'StackName',
|
|
||||||
ParameterValue: taskDefStackName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'DeleteStackName',
|
|
||||||
ParameterValue: cleanupTaskDefStackName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'TTL',
|
|
||||||
ParameterValue: '100',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: 'BUILDID',
|
|
||||||
ParameterValue: buildUid,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).promise();
|
|
||||||
core.info('Creating cleanup cluster...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise();
|
|
||||||
} catch (error) {
|
|
||||||
core.error(error);
|
|
||||||
}
|
|
||||||
const taskDefResources = await CF.describeStackResources({
|
|
||||||
StackName: taskDefStackName,
|
|
||||||
}).promise();
|
|
||||||
|
|
||||||
const baseResources = await CF.describeStackResources({ StackName: stackName }).promise();
|
|
||||||
|
|
||||||
// in the future we should offer a parameter to choose if you want the guarnteed shutdown.
|
|
||||||
core.info('Worker cluster created successfully (skipping wait for cleanup cluster to be ready)');
|
|
||||||
|
|
||||||
return {
|
|
||||||
taskDefStackName,
|
|
||||||
taskDefCloudFormation,
|
|
||||||
taskDefStackNameTTL: cleanupTaskDefStackName,
|
|
||||||
ttlCloudFormation: cleanupCloudFormation,
|
|
||||||
taskDefResources,
|
|
||||||
baseResources,
|
|
||||||
logid,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static async runTask(taskDef, ECS, CF, environment, buildUid) {
|
|
||||||
const cluster =
|
|
||||||
taskDef.baseResources.StackResources?.find((x) => x.LogicalResourceId === 'ECSCluster')?.PhysicalResourceId || '';
|
|
||||||
const taskDefinition =
|
|
||||||
taskDef.taskDefResources.StackResources?.find((x) => x.LogicalResourceId === 'TaskDefinition')
|
|
||||||
?.PhysicalResourceId || '';
|
|
||||||
const SubnetOne =
|
|
||||||
taskDef.baseResources.StackResources?.find((x) => x.LogicalResourceId === 'PublicSubnetOne')
|
|
||||||
?.PhysicalResourceId || '';
|
|
||||||
const SubnetTwo =
|
|
||||||
taskDef.baseResources.StackResources?.find((x) => x.LogicalResourceId === 'PublicSubnetTwo')
|
|
||||||
?.PhysicalResourceId || '';
|
|
||||||
const ContainerSecurityGroup =
|
|
||||||
taskDef.baseResources.StackResources?.find((x) => x.LogicalResourceId === 'ContainerSecurityGroup')
|
|
||||||
?.PhysicalResourceId || '';
|
|
||||||
const streamName =
|
|
||||||
taskDef.taskDefResources.StackResources?.find((x) => x.LogicalResourceId === 'KinesisStream')
|
|
||||||
?.PhysicalResourceId || '';
|
|
||||||
|
|
||||||
const task = await ECS.runTask({
|
|
||||||
cluster,
|
|
||||||
taskDefinition,
|
|
||||||
platformVersion: '1.4.0',
|
|
||||||
overrides: {
|
|
||||||
containerOverrides: [
|
|
||||||
{
|
|
||||||
name: taskDef.taskDefStackName,
|
|
||||||
environment: [...environment, { name: 'BUILDID', value: buildUid }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
launchType: 'FARGATE',
|
|
||||||
networkConfiguration: {
|
|
||||||
awsvpcConfiguration: {
|
|
||||||
subnets: [SubnetOne, SubnetTwo],
|
|
||||||
assignPublicIp: 'ENABLED',
|
|
||||||
securityGroups: [ContainerSecurityGroup],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).promise();
|
|
||||||
|
|
||||||
core.info('Task is starting on worker cluster');
|
|
||||||
const taskArn = task.tasks?.[0].taskArn || '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ECS.waitFor('tasksRunning', { tasks: [taskArn], cluster }).promise();
|
|
||||||
} catch (error) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
||||||
const describeTasks = await ECS.describeTasks({
|
|
||||||
tasks: [taskArn],
|
|
||||||
cluster,
|
|
||||||
}).promise();
|
|
||||||
core.info(`Task has ended ${describeTasks.tasks?.[0].containers?.[0].lastStatus}`);
|
|
||||||
core.setFailed(error);
|
|
||||||
core.error(error);
|
|
||||||
}
|
|
||||||
core.info(`Task is running on worker cluster`);
|
|
||||||
await this.streamLogsUntilTaskStops(ECS, CF, taskDef, cluster, taskArn, streamName);
|
|
||||||
await ECS.waitFor('tasksStopped', { cluster, tasks: [taskArn] }).promise();
|
|
||||||
const exitCode = (
|
|
||||||
await ECS.describeTasks({
|
|
||||||
tasks: [taskArn],
|
|
||||||
cluster,
|
|
||||||
}).promise()
|
|
||||||
).tasks?.[0].containers?.[0].exitCode;
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
try {
|
|
||||||
await this.cleanupResources(CF, taskDef);
|
|
||||||
} catch (error) {
|
|
||||||
core.warning(`failed to cleanup ${error}`);
|
|
||||||
}
|
|
||||||
core.error(`job failed with exit code ${exitCode}`);
|
|
||||||
throw new Error(`job failed with exit code ${exitCode}`);
|
|
||||||
} else {
|
|
||||||
core.info(`Task has finished successfully`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async streamLogsUntilTaskStops(ECS: AWS.ECS, CF, taskDef, clusterName, taskArn, kinesisStreamName) {
|
|
||||||
// watching logs
|
|
||||||
const kinesis = new SDK.Kinesis();
|
|
||||||
|
|
||||||
const getTaskData = async () => {
|
|
||||||
const tasks = await ECS.describeTasks({
|
|
||||||
cluster: clusterName,
|
|
||||||
tasks: [taskArn],
|
|
||||||
}).promise();
|
|
||||||
return tasks.tasks?.[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
const stream = await kinesis
|
|
||||||
.describeStream({
|
|
||||||
StreamName: kinesisStreamName,
|
|
||||||
})
|
|
||||||
.promise();
|
|
||||||
|
|
||||||
let iterator =
|
|
||||||
(
|
|
||||||
await kinesis
|
|
||||||
.getShardIterator({
|
|
||||||
ShardIteratorType: 'TRIM_HORIZON',
|
|
||||||
StreamName: stream.StreamDescription.StreamName,
|
|
||||||
ShardId: stream.StreamDescription.Shards[0].ShardId,
|
|
||||||
})
|
|
||||||
.promise()
|
|
||||||
).ShardIterator || '';
|
|
||||||
|
|
||||||
await CF.waitFor('stackCreateComplete', { StackName: taskDef.taskDefStackNameTTL }).promise();
|
|
||||||
|
|
||||||
core.info(`Task status is ${(await getTaskData())?.lastStatus}`);
|
|
||||||
|
|
||||||
const logBaseUrl = `https://${SDK.config.region}.console.aws.amazon.com/cloudwatch/home?region=${SDK.config.region}#logsV2:log-groups/log-group/${taskDef.taskDefStackName}`;
|
|
||||||
core.info(`You can also see the logs at AWS Cloud Watch: ${logBaseUrl}`);
|
|
||||||
|
|
||||||
let readingLogs = true;
|
|
||||||
let timestamp: number = 0;
|
|
||||||
while (readingLogs) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
||||||
const taskData = await getTaskData();
|
|
||||||
if (taskData?.lastStatus !== 'RUNNING') {
|
|
||||||
if (timestamp === 0) {
|
|
||||||
core.info('Task stopped, streaming end of logs');
|
|
||||||
timestamp = Date.now();
|
|
||||||
}
|
|
||||||
if (timestamp !== 0 && Date.now() - timestamp < 30000) {
|
|
||||||
core.info('Task status is not RUNNING for 30 seconds, last query for logs');
|
|
||||||
readingLogs = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const records = await kinesis
|
|
||||||
.getRecords({
|
|
||||||
ShardIterator: iterator,
|
|
||||||
})
|
|
||||||
.promise();
|
|
||||||
iterator = records.NextShardIterator || '';
|
|
||||||
if (records.Records.length > 0 && iterator) {
|
|
||||||
for (let index = 0; index < records.Records.length; index++) {
|
|
||||||
const json = JSON.parse(
|
|
||||||
zlib.gunzipSync(Buffer.from(records.Records[index].Data as string, 'base64')).toString('utf8'),
|
|
||||||
);
|
|
||||||
if (json.messageType === 'DATA_MESSAGE') {
|
|
||||||
for (let logEventsIndex = 0; logEventsIndex < json.logEvents.length; logEventsIndex++) {
|
|
||||||
if (json.logEvents[logEventsIndex].message.includes(taskDef.logid)) {
|
|
||||||
core.info('End of task logs');
|
|
||||||
readingLogs = false;
|
|
||||||
} else {
|
|
||||||
core.info(json.logEvents[logEventsIndex].message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async cleanupResources(CF, taskDef) {
|
|
||||||
await CF.deleteStack({
|
|
||||||
StackName: taskDef.taskDefStackName,
|
|
||||||
}).promise();
|
|
||||||
|
|
||||||
await CF.deleteStack({
|
|
||||||
StackName: taskDef.taskDefStackNameTTL,
|
|
||||||
}).promise();
|
|
||||||
|
|
||||||
await CF.waitFor('stackDeleteComplete', {
|
|
||||||
StackName: taskDef.taskDefStackName,
|
|
||||||
}).promise();
|
|
||||||
|
|
||||||
// Currently too slow and causes too much waiting
|
|
||||||
await CF.waitFor('stackDeleteComplete', {
|
|
||||||
StackName: taskDef.taskDefStackNameTTL,
|
|
||||||
}).promise();
|
|
||||||
|
|
||||||
core.info('Cleanup complete');
|
|
||||||
}
|
|
||||||
|
|
||||||
static onlog(batch) {
|
|
||||||
for (const log of batch) {
|
|
||||||
core.info(`log: ${log}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default AWS;
|
|
||||||
@@ -5,7 +5,35 @@ import UnityVersioning from './unity-versioning';
|
|||||||
import Versioning from './versioning';
|
import Versioning from './versioning';
|
||||||
|
|
||||||
class BuildParameters {
|
class BuildParameters {
|
||||||
static async create() {
|
public version!: string;
|
||||||
|
public customImage!: string;
|
||||||
|
public runnerTempPath: string | undefined;
|
||||||
|
public platform!: string;
|
||||||
|
public projectPath!: string;
|
||||||
|
public buildName!: string;
|
||||||
|
public buildPath!: string;
|
||||||
|
public buildFile!: string;
|
||||||
|
public buildMethod!: string;
|
||||||
|
public buildVersion!: string;
|
||||||
|
public androidVersionCode!: string;
|
||||||
|
public androidKeystoreName!: string;
|
||||||
|
public androidKeystoreBase64!: string;
|
||||||
|
public androidKeystorePass!: string;
|
||||||
|
public androidKeyaliasName!: string;
|
||||||
|
public androidKeyaliasPass!: string;
|
||||||
|
public customParameters!: string;
|
||||||
|
public sshAgent!: string;
|
||||||
|
public remoteBuildCluster!: string;
|
||||||
|
public awsStackName!: string;
|
||||||
|
public kubeConfig!: string;
|
||||||
|
public githubToken!: string;
|
||||||
|
public remoteBuildMemory!: string;
|
||||||
|
public remoteBuildCpu!: string;
|
||||||
|
public kubeVolumeSize!: string;
|
||||||
|
public kubeVolume!: string;
|
||||||
|
public chownFilesTo!: string;
|
||||||
|
|
||||||
|
static async create(): Promise<BuildParameters> {
|
||||||
const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform, Input.androidAppBundle);
|
const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform, Input.androidAppBundle);
|
||||||
|
|
||||||
const unityVersion = UnityVersioning.determineUnityVersion(Input.projectPath, Input.unityVersion);
|
const unityVersion = UnityVersioning.determineUnityVersion(Input.projectPath, Input.unityVersion);
|
||||||
@@ -33,6 +61,7 @@ class BuildParameters {
|
|||||||
androidKeyaliasName: Input.androidKeyaliasName,
|
androidKeyaliasName: Input.androidKeyaliasName,
|
||||||
androidKeyaliasPass: Input.androidKeyaliasPass,
|
androidKeyaliasPass: Input.androidKeyaliasPass,
|
||||||
customParameters: Input.customParameters,
|
customParameters: Input.customParameters,
|
||||||
|
sshAgent: Input.sshAgent,
|
||||||
chownFilesTo: Input.chownFilesTo,
|
chownFilesTo: Input.chownFilesTo,
|
||||||
remoteBuildCluster: Input.remoteBuildCluster,
|
remoteBuildCluster: Input.remoteBuildCluster,
|
||||||
awsStackName: Input.awsStackName,
|
awsStackName: Input.awsStackName,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class Docker {
|
|||||||
androidKeyaliasName,
|
androidKeyaliasName,
|
||||||
androidKeyaliasPass,
|
androidKeyaliasPass,
|
||||||
customParameters,
|
customParameters,
|
||||||
|
sshAgent,
|
||||||
chownFilesTo,
|
chownFilesTo,
|
||||||
} = parameters;
|
} = parameters;
|
||||||
|
|
||||||
@@ -79,10 +80,13 @@ class Docker {
|
|||||||
--env RUNNER_TOOL_CACHE \
|
--env RUNNER_TOOL_CACHE \
|
||||||
--env RUNNER_TEMP \
|
--env RUNNER_TEMP \
|
||||||
--env RUNNER_WORKSPACE \
|
--env RUNNER_WORKSPACE \
|
||||||
|
${sshAgent ? '--env SSH_AUTH_SOCK=/ssh-agent' : ''} \
|
||||||
--volume "/var/run/docker.sock":"/var/run/docker.sock" \
|
--volume "/var/run/docker.sock":"/var/run/docker.sock" \
|
||||||
--volume "${runnerTempPath}/_github_home":"/root" \
|
--volume "${runnerTempPath}/_github_home":"/root" \
|
||||||
--volume "${runnerTempPath}/_github_workflow":"/github/workflow" \
|
--volume "${runnerTempPath}/_github_workflow":"/github/workflow" \
|
||||||
--volume "${workspace}":"/github/workspace" \
|
--volume "${workspace}":"/github/workspace" \
|
||||||
|
${sshAgent ? `--volume ${sshAgent}:/ssh-agent` : ''} \
|
||||||
|
${sshAgent ? '--volume /home/runner/.ssh/known_hosts:/root/.ssh/known_hosts:ro' : ''} \
|
||||||
${image}`;
|
${image}`;
|
||||||
|
|
||||||
await exec(command, undefined, { silent });
|
await exec(command, undefined, { silent });
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Project from './project';
|
|||||||
import Unity from './unity';
|
import Unity from './unity';
|
||||||
import Versioning from './versioning';
|
import Versioning from './versioning';
|
||||||
import Kubernetes from './kubernetes';
|
import Kubernetes from './kubernetes';
|
||||||
import AWS from './aws';
|
import RemoteBuilder from './remote-builder/remote-builder';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Action,
|
Action,
|
||||||
@@ -25,5 +25,5 @@ export {
|
|||||||
Unity,
|
Unity,
|
||||||
Versioning,
|
Versioning,
|
||||||
Kubernetes,
|
Kubernetes,
|
||||||
AWS,
|
RemoteBuilder,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ class Input {
|
|||||||
return core.getInput('customParameters') || '';
|
return core.getInput('customParameters') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get sshAgent() {
|
||||||
|
return core.getInput('sshAgent') || '';
|
||||||
|
}
|
||||||
|
|
||||||
static get chownFilesTo() {
|
static get chownFilesTo() {
|
||||||
return core.getInput('chownFilesTo') || '';
|
return core.getInput('chownFilesTo') || '';
|
||||||
}
|
}
|
||||||
|
|||||||
253
src/model/remote-builder/aws-build-platform.ts
Normal file
253
src/model/remote-builder/aws-build-platform.ts
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
import * as SDK from 'aws-sdk';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
import RemoteBuilderSecret from './remote-builder-secret';
|
||||||
|
import RemoteBuilderEnvironmentVariable from './remote-builder-environment-variable';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import RemoteBuilderTaskDef from './remote-builder-task-def';
|
||||||
|
import RemoteBuilderConstants from './remote-builder-constants';
|
||||||
|
import AWSBuildRunner from './aws-build-runner';
|
||||||
|
|
||||||
|
class AWSBuildEnvironment {
|
||||||
|
static async runBuild(
|
||||||
|
buildId: string,
|
||||||
|
stackName: string,
|
||||||
|
image: string,
|
||||||
|
commands: string[],
|
||||||
|
mountdir: string,
|
||||||
|
workingdir: string,
|
||||||
|
environment: RemoteBuilderEnvironmentVariable[],
|
||||||
|
secrets: RemoteBuilderSecret[],
|
||||||
|
) {
|
||||||
|
const ECS = new SDK.ECS();
|
||||||
|
const CF = new SDK.CloudFormation();
|
||||||
|
const entrypoint = ['/bin/sh'];
|
||||||
|
|
||||||
|
const taskDef = await this.setupCloudFormations(
|
||||||
|
CF,
|
||||||
|
buildId,
|
||||||
|
stackName,
|
||||||
|
image,
|
||||||
|
entrypoint,
|
||||||
|
commands,
|
||||||
|
mountdir,
|
||||||
|
workingdir,
|
||||||
|
secrets,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await AWSBuildRunner.runTask(taskDef, ECS, CF, environment, buildId);
|
||||||
|
} finally {
|
||||||
|
await this.cleanupResources(CF, taskDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static async setupPlatformResources() {
|
||||||
|
// throw new Error('Method not implemented.');
|
||||||
|
// }
|
||||||
|
|
||||||
|
static getParameterTemplate(p1) {
|
||||||
|
return `
|
||||||
|
${p1}:
|
||||||
|
Type: String
|
||||||
|
Default: ''
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSecretTemplate(p1) {
|
||||||
|
return `
|
||||||
|
${p1}Secret:
|
||||||
|
Type: AWS::SecretsManager::Secret
|
||||||
|
Properties:
|
||||||
|
Name: !Join [ "", [ '${p1}', !Ref BUILDID ] ]
|
||||||
|
SecretString: !Ref ${p1}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSecretDefinitionTemplate(p1, p2) {
|
||||||
|
return `
|
||||||
|
- Name: '${p1}'
|
||||||
|
ValueFrom: !Ref ${p2}Secret
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static insertAtTemplate(template, insertionKey, insertion) {
|
||||||
|
const index = template.search(insertionKey) + insertionKey.length + '\n'.length;
|
||||||
|
template = [template.slice(0, index), insertion, template.slice(index)].join('');
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setupCloudFormations(
|
||||||
|
CF: SDK.CloudFormation,
|
||||||
|
buildUid: string,
|
||||||
|
stackName: string,
|
||||||
|
image: string,
|
||||||
|
entrypoint: string[],
|
||||||
|
commands: string[],
|
||||||
|
mountdir: string,
|
||||||
|
workingdir: string,
|
||||||
|
secrets: RemoteBuilderSecret[],
|
||||||
|
): Promise<RemoteBuilderTaskDef> {
|
||||||
|
const logid = customAlphabet(RemoteBuilderConstants.alphabet, 9)();
|
||||||
|
commands[1] += `
|
||||||
|
echo "${logid}"
|
||||||
|
`;
|
||||||
|
const taskDefStackName = `${stackName}-${buildUid}`;
|
||||||
|
let taskDefCloudFormation = this.readTaskCloudFormationTemplate();
|
||||||
|
const cleanupTaskDefStackName = `${taskDefStackName}-cleanup`;
|
||||||
|
const cleanupCloudFormation = fs.readFileSync(`${__dirname}/cloud-formations/cloudformation-stack-ttl.yml`, 'utf8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const secret of secrets) {
|
||||||
|
taskDefCloudFormation = this.insertAtTemplate(
|
||||||
|
taskDefCloudFormation,
|
||||||
|
'p1 - input',
|
||||||
|
this.getParameterTemplate(secret.ParameterKey.replace(/[^\dA-Za-z]/g, '')),
|
||||||
|
);
|
||||||
|
taskDefCloudFormation = this.insertAtTemplate(
|
||||||
|
taskDefCloudFormation,
|
||||||
|
'p2 - secret',
|
||||||
|
this.getSecretTemplate(secret.ParameterKey.replace(/[^\dA-Za-z]/g, '')),
|
||||||
|
);
|
||||||
|
taskDefCloudFormation = this.insertAtTemplate(
|
||||||
|
taskDefCloudFormation,
|
||||||
|
'p3 - container def',
|
||||||
|
this.getSecretDefinitionTemplate(secret.EnvironmentVariable, secret.ParameterKey.replace(/[^\dA-Za-z]/g, '')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const mappedSecrets = secrets.map((x) => {
|
||||||
|
return { ParameterKey: x.ParameterKey.replace(/[^\dA-Za-z]/g, ''), ParameterValue: x.ParameterValue };
|
||||||
|
});
|
||||||
|
|
||||||
|
await CF.createStack({
|
||||||
|
StackName: taskDefStackName,
|
||||||
|
TemplateBody: taskDefCloudFormation,
|
||||||
|
Parameters: [
|
||||||
|
{
|
||||||
|
ParameterKey: 'ImageUrl',
|
||||||
|
ParameterValue: image,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'ServiceName',
|
||||||
|
ParameterValue: taskDefStackName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'Command',
|
||||||
|
ParameterValue: commands.join(','),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'EntryPoint',
|
||||||
|
ParameterValue: entrypoint.join(','),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'WorkingDirectory',
|
||||||
|
ParameterValue: workingdir,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'EFSMountDirectory',
|
||||||
|
ParameterValue: mountdir,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'BUILDID',
|
||||||
|
ParameterValue: buildUid,
|
||||||
|
},
|
||||||
|
...mappedSecrets,
|
||||||
|
],
|
||||||
|
}).promise();
|
||||||
|
core.info('Creating worker cluster...');
|
||||||
|
await CF.createStack({
|
||||||
|
StackName: cleanupTaskDefStackName,
|
||||||
|
TemplateBody: cleanupCloudFormation,
|
||||||
|
Capabilities: ['CAPABILITY_IAM'],
|
||||||
|
Parameters: [
|
||||||
|
{
|
||||||
|
ParameterKey: 'StackName',
|
||||||
|
ParameterValue: taskDefStackName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'DeleteStackName',
|
||||||
|
ParameterValue: cleanupTaskDefStackName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'TTL',
|
||||||
|
ParameterValue: '100',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'BUILDID',
|
||||||
|
ParameterValue: buildUid,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).promise();
|
||||||
|
core.info('Creating cleanup cluster...');
|
||||||
|
|
||||||
|
await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise();
|
||||||
|
} catch (error) {
|
||||||
|
await AWSBuildEnvironment.handleStackCreationFailure(error, CF, taskDefStackName, taskDefCloudFormation, secrets);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDefResources = (
|
||||||
|
await CF.describeStackResources({
|
||||||
|
StackName: taskDefStackName,
|
||||||
|
}).promise()
|
||||||
|
).StackResources;
|
||||||
|
|
||||||
|
const baseResources = (await CF.describeStackResources({ StackName: stackName }).promise()).StackResources;
|
||||||
|
|
||||||
|
// in the future we should offer a parameter to choose if you want the guarnteed shutdown.
|
||||||
|
core.info('Worker cluster created successfully (skipping wait for cleanup cluster to be ready)');
|
||||||
|
|
||||||
|
return {
|
||||||
|
taskDefStackName,
|
||||||
|
taskDefCloudFormation,
|
||||||
|
taskDefStackNameTTL: cleanupTaskDefStackName,
|
||||||
|
ttlCloudFormation: cleanupCloudFormation,
|
||||||
|
taskDefResources,
|
||||||
|
baseResources,
|
||||||
|
logid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async handleStackCreationFailure(
|
||||||
|
error: any,
|
||||||
|
CF: SDK.CloudFormation,
|
||||||
|
taskDefStackName: string,
|
||||||
|
taskDefCloudFormation: string,
|
||||||
|
secrets: RemoteBuilderSecret[],
|
||||||
|
) {
|
||||||
|
core.info(JSON.stringify(secrets, undefined, 4));
|
||||||
|
core.info(taskDefCloudFormation);
|
||||||
|
const events = (await CF.describeStackEvents({ StackName: taskDefStackName }).promise()).StackEvents;
|
||||||
|
const resources = (await CF.describeStackResources({ StackName: taskDefStackName }).promise()).StackResources;
|
||||||
|
core.info(JSON.stringify(events, undefined, 4));
|
||||||
|
core.info(JSON.stringify(resources, undefined, 4));
|
||||||
|
core.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static readTaskCloudFormationTemplate(): string {
|
||||||
|
return fs.readFileSync(`${__dirname}/cloud-formations/task-def-formation.yml`, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
static async cleanupResources(CF: SDK.CloudFormation, taskDef: RemoteBuilderTaskDef) {
|
||||||
|
core.info('Cleanup starting');
|
||||||
|
await CF.deleteStack({
|
||||||
|
StackName: taskDef.taskDefStackName,
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
await CF.deleteStack({
|
||||||
|
StackName: taskDef.taskDefStackNameTTL,
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
await CF.waitFor('stackDeleteComplete', {
|
||||||
|
StackName: taskDef.taskDefStackName,
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
// Currently too slow and causes too much waiting
|
||||||
|
await CF.waitFor('stackDeleteComplete', {
|
||||||
|
StackName: taskDef.taskDefStackNameTTL,
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
core.info('Cleanup complete');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default AWSBuildEnvironment;
|
||||||
165
src/model/remote-builder/aws-build-runner.ts
Normal file
165
src/model/remote-builder/aws-build-runner.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import * as AWS from 'aws-sdk';
|
||||||
|
import RemoteBuilderEnvironmentVariable from './remote-builder-environment-variable';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import RemoteBuilderTaskDef from './remote-builder-task-def';
|
||||||
|
import * as zlib from 'zlib';
|
||||||
|
|
||||||
|
class AWSBuildRunner {
|
||||||
|
static async runTask(
|
||||||
|
taskDef: RemoteBuilderTaskDef,
|
||||||
|
ECS: AWS.ECS,
|
||||||
|
CF: AWS.CloudFormation,
|
||||||
|
environment: RemoteBuilderEnvironmentVariable[],
|
||||||
|
buildUid: string,
|
||||||
|
) {
|
||||||
|
const cluster = taskDef.baseResources?.find((x) => x.LogicalResourceId === 'ECSCluster')?.PhysicalResourceId || '';
|
||||||
|
const taskDefinition =
|
||||||
|
taskDef.taskDefResources?.find((x) => x.LogicalResourceId === 'TaskDefinition')?.PhysicalResourceId || '';
|
||||||
|
const SubnetOne =
|
||||||
|
taskDef.baseResources?.find((x) => x.LogicalResourceId === 'PublicSubnetOne')?.PhysicalResourceId || '';
|
||||||
|
const SubnetTwo =
|
||||||
|
taskDef.baseResources?.find((x) => x.LogicalResourceId === 'PublicSubnetTwo')?.PhysicalResourceId || '';
|
||||||
|
const ContainerSecurityGroup =
|
||||||
|
taskDef.baseResources?.find((x) => x.LogicalResourceId === 'ContainerSecurityGroup')?.PhysicalResourceId || '';
|
||||||
|
const streamName =
|
||||||
|
taskDef.taskDefResources?.find((x) => x.LogicalResourceId === 'KinesisStream')?.PhysicalResourceId || '';
|
||||||
|
|
||||||
|
const task = await ECS.runTask({
|
||||||
|
cluster,
|
||||||
|
taskDefinition,
|
||||||
|
platformVersion: '1.4.0',
|
||||||
|
overrides: {
|
||||||
|
containerOverrides: [
|
||||||
|
{
|
||||||
|
name: taskDef.taskDefStackName,
|
||||||
|
environment: [...environment, { name: 'BUILDID', value: buildUid }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
launchType: 'FARGATE',
|
||||||
|
networkConfiguration: {
|
||||||
|
awsvpcConfiguration: {
|
||||||
|
subnets: [SubnetOne, SubnetTwo],
|
||||||
|
assignPublicIp: 'ENABLED',
|
||||||
|
securityGroups: [ContainerSecurityGroup],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
core.info('Task is starting on worker cluster');
|
||||||
|
const taskArn = task.tasks?.[0].taskArn || '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ECS.waitFor('tasksRunning', { tasks: [taskArn], cluster }).promise();
|
||||||
|
} catch (error) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||||
|
const describeTasks = await ECS.describeTasks({
|
||||||
|
tasks: [taskArn],
|
||||||
|
cluster,
|
||||||
|
}).promise();
|
||||||
|
core.info(`Task has ended ${describeTasks.tasks?.[0].containers?.[0].lastStatus}`);
|
||||||
|
core.setFailed(error);
|
||||||
|
core.error(error);
|
||||||
|
}
|
||||||
|
core.info(`Task is running on worker cluster`);
|
||||||
|
await this.streamLogsUntilTaskStops(ECS, CF, taskDef, cluster, taskArn, streamName);
|
||||||
|
await ECS.waitFor('tasksStopped', { cluster, tasks: [taskArn] }).promise();
|
||||||
|
const exitCode = (
|
||||||
|
await ECS.describeTasks({
|
||||||
|
tasks: [taskArn],
|
||||||
|
cluster,
|
||||||
|
}).promise()
|
||||||
|
).tasks?.[0].containers?.[0].exitCode;
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
core.error(`job failed with exit code ${exitCode}`);
|
||||||
|
throw new Error(`job failed with exit code ${exitCode}`);
|
||||||
|
} else {
|
||||||
|
core.info(`Task has finished successfully`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async streamLogsUntilTaskStops(
|
||||||
|
ECS: AWS.ECS,
|
||||||
|
CF: AWS.CloudFormation,
|
||||||
|
taskDef: RemoteBuilderTaskDef,
|
||||||
|
clusterName: string,
|
||||||
|
taskArn: string,
|
||||||
|
kinesisStreamName: string,
|
||||||
|
) {
|
||||||
|
// watching logs
|
||||||
|
const kinesis = new AWS.Kinesis();
|
||||||
|
|
||||||
|
const getTaskData = async () => {
|
||||||
|
const tasks = await ECS.describeTasks({
|
||||||
|
cluster: clusterName,
|
||||||
|
tasks: [taskArn],
|
||||||
|
}).promise();
|
||||||
|
return tasks.tasks?.[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const stream = await kinesis
|
||||||
|
.describeStream({
|
||||||
|
StreamName: kinesisStreamName,
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
|
||||||
|
let iterator =
|
||||||
|
(
|
||||||
|
await kinesis
|
||||||
|
.getShardIterator({
|
||||||
|
ShardIteratorType: 'TRIM_HORIZON',
|
||||||
|
StreamName: stream.StreamDescription.StreamName,
|
||||||
|
ShardId: stream.StreamDescription.Shards[0].ShardId,
|
||||||
|
})
|
||||||
|
.promise()
|
||||||
|
).ShardIterator || '';
|
||||||
|
|
||||||
|
await CF.waitFor('stackCreateComplete', { StackName: taskDef.taskDefStackNameTTL }).promise();
|
||||||
|
|
||||||
|
core.info(`Task status is ${(await getTaskData())?.lastStatus}`);
|
||||||
|
|
||||||
|
const logBaseUrl = `https://${AWS.config.region}.console.aws.amazon.com/cloudwatch/home?region=${AWS.config.region}#logsV2:log-groups/log-group/${taskDef.taskDefStackName}`;
|
||||||
|
core.info(`You can also see the logs at AWS Cloud Watch: ${logBaseUrl}`);
|
||||||
|
|
||||||
|
let readingLogs = true;
|
||||||
|
let timestamp: number = 0;
|
||||||
|
while (readingLogs) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
const taskData = await getTaskData();
|
||||||
|
if (taskData?.lastStatus !== 'RUNNING') {
|
||||||
|
if (timestamp === 0) {
|
||||||
|
core.info('Task stopped, streaming end of logs');
|
||||||
|
timestamp = Date.now();
|
||||||
|
}
|
||||||
|
if (timestamp !== 0 && Date.now() - timestamp < 30000) {
|
||||||
|
core.info('Task status is not RUNNING for 30 seconds, last query for logs');
|
||||||
|
readingLogs = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const records = await kinesis
|
||||||
|
.getRecords({
|
||||||
|
ShardIterator: iterator,
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
iterator = records.NextShardIterator || '';
|
||||||
|
if (records.Records.length > 0 && iterator) {
|
||||||
|
for (let index = 0; index < records.Records.length; index++) {
|
||||||
|
const json = JSON.parse(
|
||||||
|
zlib.gunzipSync(Buffer.from(records.Records[index].Data as string, 'base64')).toString('utf8'),
|
||||||
|
);
|
||||||
|
if (json.messageType === 'DATA_MESSAGE') {
|
||||||
|
for (let logEventsIndex = 0; logEventsIndex < json.logEvents.length; logEventsIndex++) {
|
||||||
|
if (json.logEvents[logEventsIndex].message.includes(taskDef.logid)) {
|
||||||
|
core.info('End of task logs');
|
||||||
|
readingLogs = false;
|
||||||
|
} else {
|
||||||
|
core.info(json.logEvents[logEventsIndex].message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default AWSBuildRunner;
|
||||||
4
src/model/remote-builder/remote-builder-constants.ts
Normal file
4
src/model/remote-builder/remote-builder-constants.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class RemoteBuilderConstants {
|
||||||
|
static alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||||
|
}
|
||||||
|
export default RemoteBuilderConstants;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class RemoteBuilderEnvironmentVariable {
|
||||||
|
public name!: string;
|
||||||
|
public value!: string;
|
||||||
|
}
|
||||||
|
export default RemoteBuilderEnvironmentVariable;
|
||||||
6
src/model/remote-builder/remote-builder-secret.ts
Normal file
6
src/model/remote-builder/remote-builder-secret.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class RemoteBuilderSecret {
|
||||||
|
public ParameterKey!: string;
|
||||||
|
public EnvironmentVariable!: string;
|
||||||
|
public ParameterValue!: string;
|
||||||
|
}
|
||||||
|
export default RemoteBuilderSecret;
|
||||||
12
src/model/remote-builder/remote-builder-task-def.ts
Normal file
12
src/model/remote-builder/remote-builder-task-def.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import * as AWS from 'aws-sdk';
|
||||||
|
|
||||||
|
class RemoteBuilderTaskDef {
|
||||||
|
public taskDefStackName!: string;
|
||||||
|
public taskDefCloudFormation!: string;
|
||||||
|
public taskDefStackNameTTL!: string;
|
||||||
|
public ttlCloudFormation!: string;
|
||||||
|
public taskDefResources: AWS.CloudFormation.StackResources | undefined;
|
||||||
|
public baseResources: AWS.CloudFormation.StackResources | undefined;
|
||||||
|
public logid!: string;
|
||||||
|
}
|
||||||
|
export default RemoteBuilderTaskDef;
|
||||||
419
src/model/remote-builder/remote-builder.ts
Normal file
419
src/model/remote-builder/remote-builder.ts
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
import AWSBuildPlatform from './aws-build-platform';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import RemoteBuilderConstants from './remote-builder-constants';
|
||||||
|
import { BuildParameters } from '..';
|
||||||
|
const repositoryDirectoryName = 'repo';
|
||||||
|
const efsDirectoryName = 'data';
|
||||||
|
const cacheDirectoryName = 'cache';
|
||||||
|
|
||||||
|
class RemoteBuilder {
|
||||||
|
static SteamDeploy: boolean = false;
|
||||||
|
static async build(buildParameters: BuildParameters, baseImage) {
|
||||||
|
try {
|
||||||
|
this.SteamDeploy = process.env.STEAM_DEPLOY !== undefined || false;
|
||||||
|
const nanoid = customAlphabet(RemoteBuilderConstants.alphabet, 4);
|
||||||
|
const buildUid = `${process.env.GITHUB_RUN_NUMBER}-${buildParameters.platform
|
||||||
|
.replace('Standalone', '')
|
||||||
|
.replace('standalone', '')}-${nanoid()}`;
|
||||||
|
const defaultBranchName =
|
||||||
|
process.env.GITHUB_REF?.split('/')
|
||||||
|
.filter((x) => {
|
||||||
|
x = x[0].toUpperCase() + x.slice(1);
|
||||||
|
return x;
|
||||||
|
})
|
||||||
|
.join('') || '';
|
||||||
|
const branchName =
|
||||||
|
process.env.REMOTE_BUILDER_CACHE !== undefined ? process.env.REMOTE_BUILDER_CACHE : defaultBranchName;
|
||||||
|
const token: string = buildParameters.githubToken;
|
||||||
|
const defaultSecretsArray = [
|
||||||
|
{
|
||||||
|
ParameterKey: 'GithubToken',
|
||||||
|
EnvironmentVariable: 'GITHUB_TOKEN',
|
||||||
|
ParameterValue: token,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
await RemoteBuilder.SetupStep(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||||
|
await RemoteBuilder.BuildStep(buildUid, buildParameters, baseImage, defaultSecretsArray);
|
||||||
|
await RemoteBuilder.CompressionStep(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||||
|
await RemoteBuilder.UploadArtifacts(buildUid, buildParameters, branchName, defaultSecretsArray);
|
||||||
|
if (this.SteamDeploy) {
|
||||||
|
await RemoteBuilder.DeployToSteam(buildUid, buildParameters, defaultSecretsArray);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(error);
|
||||||
|
core.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async SetupStep(
|
||||||
|
buildUid: string,
|
||||||
|
buildParameters: BuildParameters,
|
||||||
|
branchName: string | undefined,
|
||||||
|
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
||||||
|
) {
|
||||||
|
core.info('Starting step 1/4 clone and restore cache)');
|
||||||
|
await AWSBuildPlatform.runBuild(
|
||||||
|
buildUid,
|
||||||
|
buildParameters.awsStackName,
|
||||||
|
'alpine/git',
|
||||||
|
[
|
||||||
|
'-c',
|
||||||
|
`apk update;
|
||||||
|
apk add unzip;
|
||||||
|
apk add git-lfs;
|
||||||
|
apk add jq;
|
||||||
|
# Get source repo for project to be built and game-ci repo for utilties
|
||||||
|
git clone https://${buildParameters.githubToken}@github.com/${
|
||||||
|
process.env.GITHUB_REPOSITORY
|
||||||
|
}.git ${buildUid}/${repositoryDirectoryName} -q
|
||||||
|
git clone https://${buildParameters.githubToken}@github.com/game-ci/unity-builder.git ${buildUid}/builder -q
|
||||||
|
git clone https://${buildParameters.githubToken}@github.com/game-ci/steam-deploy.git ${buildUid}/steam -q
|
||||||
|
cd /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/
|
||||||
|
git checkout $GITHUB_SHA
|
||||||
|
cd /${efsDirectoryName}/
|
||||||
|
# Look for usable cache
|
||||||
|
if [ ! -d ${cacheDirectoryName} ]; then
|
||||||
|
mkdir ${cacheDirectoryName}
|
||||||
|
fi
|
||||||
|
cd ${cacheDirectoryName}
|
||||||
|
if [ ! -d "${branchName}" ]; then
|
||||||
|
mkdir "${branchName}"
|
||||||
|
fi
|
||||||
|
cd "${branchName}"
|
||||||
|
echo ''
|
||||||
|
echo "Cached Libraries for ${branchName} from previous builds:"
|
||||||
|
ls
|
||||||
|
echo ''
|
||||||
|
ls "/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}"
|
||||||
|
libDir="/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}/Library"
|
||||||
|
if [ -d "$libDir" ]; then
|
||||||
|
rm -r "$libDir"
|
||||||
|
echo "Setup .gitignore to ignore Library folder and remove it from builds"
|
||||||
|
fi
|
||||||
|
echo 'Checking cache'
|
||||||
|
# Restore cache
|
||||||
|
latest=$(ls -t | head -1)
|
||||||
|
if [ ! -z "$latest" ]; then
|
||||||
|
echo "Library cache exists from build $latest from ${branchName}"
|
||||||
|
echo 'Creating empty Library folder for cache'
|
||||||
|
mkdir $libDir
|
||||||
|
unzip -q $latest -d $libDir
|
||||||
|
# purge cache
|
||||||
|
${process.env.PURGE_REMOTE_BUILDER_CACHE === undefined ? '#' : ''} rm -r $libDir
|
||||||
|
else
|
||||||
|
echo 'Cache does not exist'
|
||||||
|
fi
|
||||||
|
# Print out important directories
|
||||||
|
echo ''
|
||||||
|
echo 'Repo:'
|
||||||
|
ls /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/
|
||||||
|
echo ''
|
||||||
|
echo 'Project:'
|
||||||
|
ls /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}
|
||||||
|
echo ''
|
||||||
|
echo 'Library:'
|
||||||
|
ls /${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}/Library/
|
||||||
|
echo ''
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
`/${efsDirectoryName}`,
|
||||||
|
`/${efsDirectoryName}/`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'GITHUB_SHA',
|
||||||
|
value: process.env.GITHUB_SHA || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultSecretsArray,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async BuildStep(
|
||||||
|
buildUid: string,
|
||||||
|
buildParameters: BuildParameters,
|
||||||
|
baseImage: any,
|
||||||
|
defaultSecretsArray: any[],
|
||||||
|
) {
|
||||||
|
const buildSecrets = new Array();
|
||||||
|
|
||||||
|
buildSecrets.push(...defaultSecretsArray);
|
||||||
|
|
||||||
|
if (process.env.UNITY_LICENSE)
|
||||||
|
buildSecrets.push({
|
||||||
|
ParameterKey: 'UnityLicense',
|
||||||
|
EnvironmentVariable: 'UNITY_LICENSE',
|
||||||
|
ParameterValue: process.env.UNITY_LICENSE,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.UNITY_EMAIL)
|
||||||
|
buildSecrets.push({
|
||||||
|
ParameterKey: 'UnityEmail',
|
||||||
|
EnvironmentVariable: 'UNITY_EMAIL',
|
||||||
|
ParameterValue: process.env.UNITY_EMAIL,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.UNITY_PASSWORD)
|
||||||
|
buildSecrets.push({
|
||||||
|
ParameterKey: 'UnityPassword',
|
||||||
|
EnvironmentVariable: 'UNITY_PASSWORD',
|
||||||
|
ParameterValue: process.env.UNITY_PASSWORD,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.UNITY_SERIAL)
|
||||||
|
buildSecrets.push({
|
||||||
|
ParameterKey: 'UnitySerial',
|
||||||
|
EnvironmentVariable: 'UNITY_SERIAL',
|
||||||
|
ParameterValue: process.env.UNITY_SERIAL,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (buildParameters.androidKeystoreBase64)
|
||||||
|
buildSecrets.push({
|
||||||
|
ParameterKey: 'AndroidKeystoreBase64',
|
||||||
|
EnvironmentVariable: 'ANDROID_KEYSTORE_BASE64',
|
||||||
|
ParameterValue: buildParameters.androidKeystoreBase64,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (buildParameters.androidKeystorePass)
|
||||||
|
buildSecrets.push({
|
||||||
|
ParameterKey: 'AndroidKeystorePass',
|
||||||
|
EnvironmentVariable: 'ANDROID_KEYSTORE_PASS',
|
||||||
|
ParameterValue: buildParameters.androidKeystorePass,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (buildParameters.androidKeyaliasPass)
|
||||||
|
buildSecrets.push({
|
||||||
|
ParameterKey: 'AndroidKeyAliasPass',
|
||||||
|
EnvironmentVariable: 'AWS_ACCESS_KEY_ALIAS_PASS',
|
||||||
|
ParameterValue: buildParameters.androidKeyaliasPass,
|
||||||
|
});
|
||||||
|
core.info('Starting part 2/4 (build unity project)');
|
||||||
|
await AWSBuildPlatform.runBuild(
|
||||||
|
buildUid,
|
||||||
|
buildParameters.awsStackName,
|
||||||
|
baseImage.toString(),
|
||||||
|
[
|
||||||
|
'-c',
|
||||||
|
`
|
||||||
|
cp -r /${efsDirectoryName}/${buildUid}/builder/dist/default-build-script/ /UnityBuilderAction;
|
||||||
|
cp -r /${efsDirectoryName}/${buildUid}/builder/dist/entrypoint.sh /entrypoint.sh;
|
||||||
|
cp -r /${efsDirectoryName}/${buildUid}/builder/dist/steps/ /steps;
|
||||||
|
chmod -R +x /entrypoint.sh;
|
||||||
|
chmod -R +x /steps;
|
||||||
|
/entrypoint.sh;
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
`/${efsDirectoryName}`,
|
||||||
|
`/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'ContainerMemory',
|
||||||
|
value: buildParameters.remoteBuildMemory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ContainerCpu',
|
||||||
|
value: buildParameters.remoteBuildCpu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'GITHUB_WORKSPACE',
|
||||||
|
value: `/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PROJECT_PATH',
|
||||||
|
value: buildParameters.projectPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BUILD_PATH',
|
||||||
|
value: buildParameters.buildPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BUILD_FILE',
|
||||||
|
value: buildParameters.buildFile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BUILD_NAME',
|
||||||
|
value: buildParameters.buildName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BUILD_METHOD',
|
||||||
|
value: buildParameters.buildMethod,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CUSTOM_PARAMETERS',
|
||||||
|
value: buildParameters.customParameters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BUILD_TARGET',
|
||||||
|
value: buildParameters.platform,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ANDROID_VERSION_CODE',
|
||||||
|
value: buildParameters.androidVersionCode.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ANDROID_KEYSTORE_NAME',
|
||||||
|
value: buildParameters.androidKeystoreName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ANDROID_KEYALIAS_NAME',
|
||||||
|
value: buildParameters.androidKeyaliasName,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
buildSecrets,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async CompressionStep(
|
||||||
|
buildUid: string,
|
||||||
|
buildParameters: BuildParameters,
|
||||||
|
branchName: string | undefined,
|
||||||
|
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
||||||
|
) {
|
||||||
|
core.info('Starting step 3/4 build compression');
|
||||||
|
// Cleanup
|
||||||
|
await AWSBuildPlatform.runBuild(
|
||||||
|
buildUid,
|
||||||
|
buildParameters.awsStackName,
|
||||||
|
'alpine',
|
||||||
|
[
|
||||||
|
'-c',
|
||||||
|
`
|
||||||
|
apk update
|
||||||
|
apk add zip
|
||||||
|
cd Library
|
||||||
|
zip -r lib-${buildUid}.zip .*
|
||||||
|
mv lib-${buildUid}.zip /${efsDirectoryName}/${cacheDirectoryName}/${branchName}/lib-${buildUid}.zip
|
||||||
|
cd ../../
|
||||||
|
zip -r build-${buildUid}.zip ${buildParameters.buildPath}/*
|
||||||
|
mv build-${buildUid}.zip /${efsDirectoryName}/${buildUid}/build-${buildUid}.zip
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
`/${efsDirectoryName}`,
|
||||||
|
`/${efsDirectoryName}/${buildUid}/${repositoryDirectoryName}/${buildParameters.projectPath}`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'GITHUB_SHA',
|
||||||
|
value: process.env.GITHUB_SHA || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultSecretsArray,
|
||||||
|
);
|
||||||
|
core.info('compression step complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async UploadArtifacts(
|
||||||
|
buildUid: string,
|
||||||
|
buildParameters: BuildParameters,
|
||||||
|
branchName: string | undefined,
|
||||||
|
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
||||||
|
) {
|
||||||
|
core.info('Starting step 4/4 upload build to s3');
|
||||||
|
await AWSBuildPlatform.runBuild(
|
||||||
|
buildUid,
|
||||||
|
buildParameters.awsStackName,
|
||||||
|
'amazon/aws-cli',
|
||||||
|
[
|
||||||
|
'-c',
|
||||||
|
`
|
||||||
|
aws s3 cp ${buildUid}/build-${buildUid}.zip s3://game-ci-storage/
|
||||||
|
# no need to upload Library cache for now
|
||||||
|
# aws s3 cp /${efsDirectoryName}/${cacheDirectoryName}/${branchName}/lib-${buildUid}.zip s3://game-ci-storage/
|
||||||
|
${this.SteamDeploy ? '#' : ''} rm -r ${buildUid}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
`/${efsDirectoryName}`,
|
||||||
|
`/${efsDirectoryName}/`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'GITHUB_SHA',
|
||||||
|
value: process.env.GITHUB_SHA || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'AWS_DEFAULT_REGION',
|
||||||
|
value: process.env.AWS_DEFAULT_REGION || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
ParameterKey: 'AWSAccessKeyID',
|
||||||
|
EnvironmentVariable: 'AWS_ACCESS_KEY_ID',
|
||||||
|
ParameterValue: process.env.AWS_ACCESS_KEY_ID || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ParameterKey: 'AWSSecretAccessKey',
|
||||||
|
EnvironmentVariable: 'AWS_SECRET_ACCESS_KEY',
|
||||||
|
ParameterValue: process.env.AWS_SECRET_ACCESS_KEY || '',
|
||||||
|
},
|
||||||
|
...defaultSecretsArray,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async DeployToSteam(
|
||||||
|
buildUid: string,
|
||||||
|
buildParameters: BuildParameters,
|
||||||
|
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
||||||
|
) {
|
||||||
|
core.info('Starting steam deployment');
|
||||||
|
await AWSBuildPlatform.runBuild(
|
||||||
|
buildUid,
|
||||||
|
buildParameters.awsStackName,
|
||||||
|
'cm2network/steamcmd:root',
|
||||||
|
[
|
||||||
|
'-c',
|
||||||
|
`
|
||||||
|
ls
|
||||||
|
ls /
|
||||||
|
cp -r /${efsDirectoryName}/${buildUid}/steam/action/entrypoint.sh /entrypoint.sh;
|
||||||
|
cp -r /${efsDirectoryName}/${buildUid}/steam/action/steps/ /steps;
|
||||||
|
chmod -R +x /entrypoint.sh;
|
||||||
|
chmod -R +x /steps;
|
||||||
|
/entrypoint.sh;
|
||||||
|
rm -r /${efsDirectoryName}/${buildUid}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
`/${efsDirectoryName}`,
|
||||||
|
`/${efsDirectoryName}/${buildUid}/steam/action/`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'GITHUB_SHA',
|
||||||
|
value: process.env.GITHUB_SHA || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
EnvironmentVariable: 'INPUT_APPID',
|
||||||
|
ParameterKey: 'appId',
|
||||||
|
ParameterValue: process.env.APP_ID || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvironmentVariable: 'INPUT_BUILDDESCRIPTION',
|
||||||
|
ParameterKey: 'buildDescription',
|
||||||
|
ParameterValue: process.env.BUILD_DESCRIPTION || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvironmentVariable: 'INPUT_ROOTPATH',
|
||||||
|
ParameterKey: 'rootPath',
|
||||||
|
ParameterValue: process.env.ROOT_PATH || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvironmentVariable: 'INPUT_RELEASEBRANCH',
|
||||||
|
ParameterKey: 'releaseBranch',
|
||||||
|
ParameterValue: process.env.RELEASE_BRANCH || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvironmentVariable: 'INPUT_LOCALCONTENTSERVER',
|
||||||
|
ParameterKey: 'localContentServer',
|
||||||
|
ParameterValue: process.env.LOCAL_CONTENT_SERVER || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvironmentVariable: 'INPUT_PREVIEWENABLED',
|
||||||
|
ParameterKey: 'previewEnabled',
|
||||||
|
ParameterValue: process.env.PREVIEW_ENABLED || '',
|
||||||
|
},
|
||||||
|
...defaultSecretsArray,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default RemoteBuilder;
|
||||||
@@ -14,8 +14,8 @@ describe('Unity Versioning', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('read', () => {
|
describe('read', () => {
|
||||||
it('does not throw', () => {
|
it('throws for invalid path', () => {
|
||||||
expect(() => UnityVersioning.read('')).not.toThrow();
|
expect(() => UnityVersioning.read('')).toThrow(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads from test-project', () => {
|
it('reads from test-project', () => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as core from '@actions/core';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@@ -17,8 +16,7 @@ export default class UnityVersioning {
|
|||||||
static read(projectPath) {
|
static read(projectPath) {
|
||||||
const filePath = path.join(projectPath, 'ProjectSettings', 'ProjectVersion.txt');
|
const filePath = path.join(projectPath, 'ProjectSettings', 'ProjectVersion.txt');
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
core.warning(`Could not find "${filePath}", keeping unityVersion as "auto"`);
|
throw new Error(`Project settings file not found at "${filePath}". Have you correctly set the projectPath?`);
|
||||||
return 'auto';
|
|
||||||
}
|
}
|
||||||
return UnityVersioning.parse(fs.readFileSync(filePath, 'utf8'));
|
return UnityVersioning.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ export default class Versioning {
|
|||||||
* Get the tag if there is one pointing at HEAD
|
* Get the tag if there is one pointing at HEAD
|
||||||
*/
|
*/
|
||||||
static async getTag() {
|
static async getTag() {
|
||||||
return this.git(['tag', '--points-at', 'HEAD']);
|
return (await this.git(['tag', '--points-at', 'HEAD'])).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
50
yarn.lock
50
yarn.lock
@@ -1349,13 +1349,13 @@ browser-process-hrtime@^1.0.0:
|
|||||||
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
|
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
|
||||||
|
|
||||||
browserslist@^4.14.5:
|
browserslist@^4.14.5:
|
||||||
version "4.16.4"
|
version "4.16.6"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.4.tgz#7ebf913487f40caf4637b892b268069951c35d58"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
|
||||||
integrity sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==
|
integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite "^1.0.30001208"
|
caniuse-lite "^1.0.30001219"
|
||||||
colorette "^1.2.2"
|
colorette "^1.2.2"
|
||||||
electron-to-chromium "^1.3.712"
|
electron-to-chromium "^1.3.723"
|
||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
node-releases "^1.1.71"
|
node-releases "^1.1.71"
|
||||||
|
|
||||||
@@ -1443,10 +1443,10 @@ camelcase@^6.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
|
||||||
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
|
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001208:
|
caniuse-lite@^1.0.30001219:
|
||||||
version "1.0.30001214"
|
version "1.0.30001230"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001214.tgz#70f153c78223515c6d37a9fde6cd69250da9d872"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71"
|
||||||
integrity sha512-O2/SCpuaU3eASWVaesQirZv1MSjUNOvmugaD8zNSJqw6Vv5SGwoOpA9LJs3pNPfM745nxqPvfZY3MQKY4AKHYg==
|
integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==
|
||||||
|
|
||||||
capture-exit@^2.0.0:
|
capture-exit@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@@ -1868,10 +1868,10 @@ ecc-jsbn@~0.1.1:
|
|||||||
jsbn "~0.1.0"
|
jsbn "~0.1.0"
|
||||||
safer-buffer "^2.1.0"
|
safer-buffer "^2.1.0"
|
||||||
|
|
||||||
electron-to-chromium@^1.3.712:
|
electron-to-chromium@^1.3.723:
|
||||||
version "1.3.717"
|
version "1.3.738"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz#78d4c857070755fb58ab64bcc173db1d51cbc25f"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.738.tgz#aec24b091c82acbfabbdcce08076a703941d17ca"
|
||||||
integrity sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==
|
integrity sha512-vCMf4gDOpEylPSLPLSwAEsz+R3ShP02Y3cAKMZvTqule3XcPp7tgc/0ESI7IS6ZeyBlGClE50N53fIOkcIVnpw==
|
||||||
|
|
||||||
emittery@^0.7.1:
|
emittery@^0.7.1:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
@@ -4048,9 +4048,9 @@ node-notifier@^8.0.0:
|
|||||||
which "^2.0.2"
|
which "^2.0.2"
|
||||||
|
|
||||||
node-releases@^1.1.71:
|
node-releases@^1.1.71:
|
||||||
version "1.1.71"
|
version "1.1.72"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
|
||||||
integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==
|
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
|
||||||
|
|
||||||
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
|
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
@@ -4075,9 +4075,9 @@ normalize-path@^3.0.0:
|
|||||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||||
|
|
||||||
normalize-url@^4.1.0:
|
normalize-url@^4.1.0:
|
||||||
version "4.5.0"
|
version "4.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
|
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
|
||||||
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
|
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
|
||||||
|
|
||||||
npm-run-path@^2.0.0:
|
npm-run-path@^2.0.0:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
@@ -4378,9 +4378,9 @@ path-key@^3.0.0, path-key@^3.1.0:
|
|||||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||||
|
|
||||||
path-parse@^1.0.6:
|
path-parse@^1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
path-type@^2.0.0:
|
path-type@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@@ -5701,9 +5701,9 @@ write-file-atomic@^3.0.0:
|
|||||||
typedarray-to-buffer "^3.1.5"
|
typedarray-to-buffer "^3.1.5"
|
||||||
|
|
||||||
ws@^6.1.0:
|
ws@^6.1.0:
|
||||||
version "6.2.1"
|
version "6.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
|
||||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==
|
||||||
dependencies:
|
dependencies:
|
||||||
async-limiter "~1.0.0"
|
async-limiter "~1.0.0"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user