mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-06-01 14:26:17 -07:00
Compare commits
44 Commits
v4.1.2
...
fix/secure
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f80e4f66d4 | ||
|
|
b2327008ed | ||
|
|
b3bd405399 | ||
|
|
8a41533779 | ||
|
|
9d475434d3 | ||
|
|
f3849ee1c9 | ||
|
|
0c82a58873 | ||
|
|
1d4ee0697f | ||
|
|
3a2abf9037 | ||
|
|
cfdebb67c1 | ||
|
|
ab64768ceb | ||
|
|
00fa0d3772 | ||
|
|
d587557287 | ||
|
|
6e0bf17345 | ||
|
|
2822af505e | ||
|
|
8ec161b981 | ||
|
|
88a89c94a0 | ||
|
|
f7f3f70c57 | ||
|
|
c6c8236152 | ||
|
|
9e91ca9749 | ||
|
|
9cd9f7e0e7 | ||
|
|
0b822c28fb | ||
|
|
65607f9ebb | ||
|
|
a1ebdb7abd | ||
|
|
3b26780ddf | ||
|
|
819c2511e0 | ||
|
|
81ed299e10 | ||
|
|
9d6bdcbdc5 | ||
|
|
3ae9ec8536 | ||
|
|
83c85328dd | ||
|
|
b11b6a6f2c | ||
|
|
461ecf7cea | ||
|
|
f2250e958e | ||
|
|
dd427466ce | ||
|
|
0c16aab353 | ||
|
|
fc0a52b805 | ||
|
|
e820c9ce7b | ||
|
|
f4d2cceeb5 | ||
|
|
4ae184ca89 | ||
|
|
082ea39498 | ||
|
|
e73b48fb38 | ||
|
|
2800d14403 | ||
|
|
5ba81971e2 | ||
|
|
ff23166e30 |
@@ -1,22 +1,11 @@
|
|||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": ["jest", "@typescript-eslint", "prettier", "unicorn"],
|
||||||
"jest",
|
"extends": ["plugin:unicorn/recommended", "plugin:github/recommended", "plugin:prettier/recommended"],
|
||||||
"@typescript-eslint",
|
|
||||||
"prettier",
|
|
||||||
"unicorn"
|
|
||||||
],
|
|
||||||
"extends": [
|
|
||||||
"plugin:unicorn/recommended",
|
|
||||||
"plugin:github/recommended",
|
|
||||||
"plugin:prettier/recommended"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2020,
|
"ecmaVersion": 2020,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"extraFileExtensions": [
|
"extraFileExtensions": [".mjs"],
|
||||||
".mjs"
|
|
||||||
],
|
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"impliedStrict": true
|
"impliedStrict": true
|
||||||
},
|
},
|
||||||
@@ -25,7 +14,8 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"node": true,
|
"node": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"jest/globals": true
|
"jest/globals": true,
|
||||||
|
"es2020": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
// Error out for code formatting errors
|
// Error out for code formatting errors
|
||||||
@@ -33,10 +23,7 @@
|
|||||||
// Namespaces or sometimes needed
|
// Namespaces or sometimes needed
|
||||||
"import/no-namespace": "off",
|
"import/no-namespace": "off",
|
||||||
// Properly format comments
|
// Properly format comments
|
||||||
"spaced-comment": [
|
"spaced-comment": ["error", "always"],
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"lines-around-comment": [
|
"lines-around-comment": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
@@ -71,12 +58,7 @@
|
|||||||
// Enforce camelCase
|
// Enforce camelCase
|
||||||
"camelcase": "error",
|
"camelcase": "error",
|
||||||
// Allow forOfStatements
|
// Allow forOfStatements
|
||||||
"no-restricted-syntax": [
|
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
|
||||||
"error",
|
|
||||||
"ForInStatement",
|
|
||||||
"LabeledStatement",
|
|
||||||
"WithStatement"
|
|
||||||
],
|
|
||||||
// Continue is viable in forOf loops in generators
|
// Continue is viable in forOf loops in generators
|
||||||
"no-continue": "off",
|
"no-continue": "off",
|
||||||
// From experience, named exports are almost always desired. I got tired of this rule
|
// From experience, named exports are almost always desired. I got tired of this rule
|
||||||
@@ -96,5 +78,13 @@
|
|||||||
"unicorn/prefer-spread": "off",
|
"unicorn/prefer-spread": "off",
|
||||||
// Temp disable to prevent mixing changes with other PRs
|
// Temp disable to prevent mixing changes with other PRs
|
||||||
"i18n-text/no-en": "off"
|
"i18n-text/no-en": "off"
|
||||||
}
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["jest.setup.js"],
|
||||||
|
"rules": {
|
||||||
|
"import/no-commonjs": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
@@ -12,6 +12,9 @@
|
|||||||
|
|
||||||
#### Successful Workflow Run Link
|
#### Successful Workflow Run Link
|
||||||
|
|
||||||
|
PRs don't have access to secrets so you will need to provide a link to a successful run of the workflows from your own
|
||||||
|
repo.
|
||||||
|
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
#### Checklist
|
#### Checklist
|
||||||
|
|||||||
2
.github/workflows/activation.yml
vendored
2
.github/workflows/activation.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
id: requestActivationFile
|
id: requestActivationFile
|
||||||
uses: game-ci/unity-request-activation-file@v2.0-alpha-1
|
uses: game-ci/unity-request-activation-file@v2.0-alpha-1
|
||||||
- name: Upload activation file
|
- name: Upload activation file
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.requestActivationFile.outputs.filePath }}
|
name: ${{ steps.requestActivationFile.outputs.filePath }}
|
||||||
path: ${{ steps.requestActivationFile.outputs.filePath }}
|
path: ${{ steps.requestActivationFile.outputs.filePath }}
|
||||||
|
|||||||
20
.github/workflows/build-tests-mac.yml
vendored
20
.github/workflows/build-tests-mac.yml
vendored
@@ -12,19 +12,26 @@ jobs:
|
|||||||
buildForAllPlatformsMacOS:
|
buildForAllPlatformsMacOS:
|
||||||
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
|
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
continue-on-error: true
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
projectPath:
|
projectPath:
|
||||||
- test-project
|
- test-project
|
||||||
unityVersion:
|
unityVersion:
|
||||||
- 2021.3.32f1
|
- 2021.3.45f1
|
||||||
- 2022.3.13f1
|
- 2022.3.13f1
|
||||||
- 2023.1.19f1
|
|
||||||
- 2023.2.2f1
|
- 2023.2.2f1
|
||||||
targetPlatform:
|
targetPlatform:
|
||||||
- StandaloneOSX # Build a MacOS executable
|
- StandaloneOSX # Build a MacOS executable
|
||||||
- iOS # Build an iOS executable
|
- iOS # Build an iOS executable
|
||||||
|
include:
|
||||||
|
# Additionally test enableGpu build for a standalone windows target
|
||||||
|
- unityVersion: 6000.0.36f1
|
||||||
|
targetPlatform: StandaloneOSX
|
||||||
|
- unityVersion: 6000.0.36f1
|
||||||
|
targetPlatform: StandaloneOSX
|
||||||
|
buildProfile: 'Assets/Settings/Build Profiles/Sample macOS Build Profile.asset'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
###########################
|
###########################
|
||||||
@@ -37,7 +44,7 @@ jobs:
|
|||||||
###########################
|
###########################
|
||||||
# Cache #
|
# Cache #
|
||||||
###########################
|
###########################
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ matrix.projectPath }}/Library
|
path: ${{ matrix.projectPath }}/Library
|
||||||
key: Library-${{ matrix.projectPath }}-macos-${{ matrix.targetPlatform }}
|
key: Library-${{ matrix.projectPath }}-macos-${{ matrix.targetPlatform }}
|
||||||
@@ -60,10 +67,13 @@ jobs:
|
|||||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||||
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
||||||
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||||
with:
|
with:
|
||||||
|
buildName: 'GameCI Test Build'
|
||||||
projectPath: ${{ matrix.projectPath }}
|
projectPath: ${{ matrix.projectPath }}
|
||||||
unityVersion: ${{ matrix.unityVersion }}
|
unityVersion: ${{ matrix.unityVersion }}
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
targetPlatform: ${{ matrix.targetPlatform }}
|
||||||
|
buildProfile: ${{ matrix.buildProfile }}
|
||||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
||||||
# We use dirty build because we are replacing the default project settings file above
|
# We use dirty build because we are replacing the default project settings file above
|
||||||
allowDirtyBuild: true
|
allowDirtyBuild: true
|
||||||
@@ -71,8 +81,8 @@ jobs:
|
|||||||
###########################
|
###########################
|
||||||
# Upload #
|
# Upload #
|
||||||
###########################
|
###########################
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Build MacOS (${{ matrix.unityVersion }})
|
name: Build ${{ matrix.targetPlatform }} on MacOS (${{ matrix.unityVersion }})${{ matrix.buildProfile && ' With Build Profile' || '' }}
|
||||||
path: build
|
path: build
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
|||||||
81
.github/workflows/build-tests-ubuntu.yml
vendored
81
.github/workflows/build-tests-ubuntu.yml
vendored
@@ -36,7 +36,8 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
buildForAllPlatformsUbuntu:
|
buildForAllPlatformsUbuntu:
|
||||||
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
|
name:
|
||||||
|
"${{ matrix.targetPlatform }} on ${{ matrix.unityVersion}}${{startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }}"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -49,16 +50,60 @@ jobs:
|
|||||||
unityVersion:
|
unityVersion:
|
||||||
- 2021.3.32f1
|
- 2021.3.32f1
|
||||||
- 2022.3.13f1
|
- 2022.3.13f1
|
||||||
- 2023.1.19f1
|
|
||||||
- 2023.2.2f1
|
- 2023.2.2f1
|
||||||
targetPlatform:
|
targetPlatform:
|
||||||
- StandaloneOSX # Build a macOS standalone (Intel 64-bit) with mono backend.
|
- StandaloneOSX # Build a macOS standalone (Intel 64-bit) with mono backend.
|
||||||
- StandaloneWindows64 # Build a Windows 64-bit standalone with mono backend.
|
- StandaloneWindows64 # Build a Windows 64-bit standalone with mono backend.
|
||||||
- StandaloneLinux64 # Build a Linux 64-bit standalone with mono backend.
|
- StandaloneLinux64 # Build a Linux 64-bit standalone with mono/il2cpp backend.
|
||||||
- iOS # Build an iOS player.
|
- iOS # Build an iOS project.
|
||||||
- Android # Build an Android .apk.
|
- Android # Build an Android .apk.
|
||||||
- WebGL # WebGL.
|
- WebGL # WebGL.
|
||||||
|
buildWithIl2cpp:
|
||||||
|
- false
|
||||||
|
- true
|
||||||
|
additionalParameters:
|
||||||
|
- -param value
|
||||||
|
- -standaloneBuildSubtarget Server
|
||||||
|
# Skipping configurations that are not supported
|
||||||
|
exclude:
|
||||||
|
# No il2cpp support on Linux Host
|
||||||
|
- targetPlatform: StandaloneOSX
|
||||||
|
buildWithIl2cpp: true
|
||||||
|
- targetPlatform: StandaloneWindows64
|
||||||
|
buildWithIl2cpp: true
|
||||||
|
# Only builds with Il2cpp
|
||||||
|
- targetPlatform: iOS
|
||||||
|
buildWithIl2cpp: false
|
||||||
|
- targetPlatform: Android
|
||||||
|
buildWithIl2cpp: false
|
||||||
|
- targetPlatform: WebGL
|
||||||
|
buildWithIl2cpp: false
|
||||||
|
# No dedicated server support
|
||||||
|
- targetPlatform: WebGL
|
||||||
|
additionalParameters: -standaloneBuildSubtarget Server
|
||||||
|
- targetPlatform: Android
|
||||||
|
additionalParameters: -standaloneBuildSubtarget Server
|
||||||
|
- targetPlatform: iOS
|
||||||
|
additionalParameters: -standaloneBuildSubtarget Server
|
||||||
|
# No dedicated server support on Linux Host
|
||||||
|
- targetPlatform: StandaloneOSX
|
||||||
|
additionalParameters: -standaloneBuildSubtarget Server
|
||||||
|
# No il2cpp dedicated server support on Linux Host
|
||||||
|
- targetPlatform: StandaloneWindows64
|
||||||
|
additionalParameters: -standaloneBuildSubtarget Server
|
||||||
|
buildWithIl2cpp: true
|
||||||
|
include:
|
||||||
|
- unityVersion: 6000.0.36f1
|
||||||
|
targetPlatform: WebGL
|
||||||
|
- unityVersion: 6000.0.36f1
|
||||||
|
targetPlatform: WebGL
|
||||||
|
buildProfile: 'Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Clear Space for Android Build
|
||||||
|
if: matrix.targetPlatform == 'Android'
|
||||||
|
uses: jlumbroso/free-disk-space@v1.3.1
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# Checkout #
|
# Checkout #
|
||||||
###########################
|
###########################
|
||||||
@@ -69,7 +114,7 @@ jobs:
|
|||||||
###########################
|
###########################
|
||||||
# Cache #
|
# Cache #
|
||||||
###########################
|
###########################
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ matrix.projectPath }}/Library
|
path: ${{ matrix.projectPath }}/Library
|
||||||
key: Library-${{ matrix.projectPath }}-ubuntu-${{ matrix.targetPlatform }}
|
key: Library-${{ matrix.projectPath }}-ubuntu-${{ matrix.targetPlatform }}
|
||||||
@@ -77,6 +122,14 @@ jobs:
|
|||||||
Library-${{ matrix.projectPath }}-ubuntu-
|
Library-${{ matrix.projectPath }}-ubuntu-
|
||||||
Library-
|
Library-
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# Set Scripting Backend #
|
||||||
|
###########################
|
||||||
|
- name: Set Scripting Backend To il2cpp
|
||||||
|
if: matrix.buildWithIl2cpp == true
|
||||||
|
run: |
|
||||||
|
mv -f "./test-project/ProjectSettings/ProjectSettingsIl2cpp.asset" "./test-project/ProjectSettings/ProjectSettings.asset"
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# Build #
|
# Build #
|
||||||
###########################
|
###########################
|
||||||
@@ -88,11 +141,14 @@ jobs:
|
|||||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
|
buildName: 'GameCI Test Build'
|
||||||
projectPath: ${{ matrix.projectPath }}
|
projectPath: ${{ matrix.projectPath }}
|
||||||
|
buildProfile: ${{ matrix.buildProfile }}
|
||||||
unityVersion: ${{ matrix.unityVersion }}
|
unityVersion: ${{ matrix.unityVersion }}
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
targetPlatform: ${{ matrix.targetPlatform }}
|
||||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
|
||||||
providerStrategy: ${{ matrix.providerStrategy }}
|
providerStrategy: ${{ matrix.providerStrategy }}
|
||||||
|
allowDirtyBuild: true
|
||||||
|
|
||||||
- name: Sleep for Retry
|
- name: Sleep for Retry
|
||||||
if: ${{ steps.build-1.outcome == 'failure' }}
|
if: ${{ steps.build-1.outcome == 'failure' }}
|
||||||
@@ -108,10 +164,12 @@ jobs:
|
|||||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
|
buildName: 'GameCI Test Build'
|
||||||
projectPath: ${{ matrix.projectPath }}
|
projectPath: ${{ matrix.projectPath }}
|
||||||
|
buildProfile: ${{ matrix.buildProfile }}
|
||||||
unityVersion: ${{ matrix.unityVersion }}
|
unityVersion: ${{ matrix.unityVersion }}
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
targetPlatform: ${{ matrix.targetPlatform }}
|
||||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
|
||||||
providerStrategy: ${{ matrix.providerStrategy }}
|
providerStrategy: ${{ matrix.providerStrategy }}
|
||||||
allowDirtyBuild: true
|
allowDirtyBuild: true
|
||||||
|
|
||||||
@@ -128,18 +186,21 @@ jobs:
|
|||||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
|
buildName: 'GameCI Test Build'
|
||||||
projectPath: ${{ matrix.projectPath }}
|
projectPath: ${{ matrix.projectPath }}
|
||||||
|
buildProfile: ${{ matrix.buildProfile }}
|
||||||
unityVersion: ${{ matrix.unityVersion }}
|
unityVersion: ${{ matrix.unityVersion }}
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
targetPlatform: ${{ matrix.targetPlatform }}
|
||||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
|
||||||
providerStrategy: ${{ matrix.providerStrategy }}
|
providerStrategy: ${{ matrix.providerStrategy }}
|
||||||
allowDirtyBuild: true
|
allowDirtyBuild: true
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# Upload #
|
# Upload #
|
||||||
###########################
|
###########################
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Build Ubuntu (${{ matrix.unityVersion }})
|
name:
|
||||||
|
"Build ${{ matrix.targetPlatform }}${{ startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }} on Ubuntu (${{ matrix.unityVersion }}_il2cpp_${{ matrix.buildWithIl2cpp }}_params_${{ matrix.additionalParameters }})"
|
||||||
path: build
|
path: build
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
|||||||
32
.github/workflows/build-tests-windows.yml
vendored
32
.github/workflows/build-tests-windows.yml
vendored
@@ -20,14 +20,26 @@ jobs:
|
|||||||
unityVersion:
|
unityVersion:
|
||||||
- 2021.3.32f1
|
- 2021.3.32f1
|
||||||
- 2022.3.13f1
|
- 2022.3.13f1
|
||||||
- 2023.1.19f1
|
|
||||||
- 2023.2.2f1
|
- 2023.2.2f1
|
||||||
targetPlatform:
|
targetPlatform:
|
||||||
- Android # Build an Android apk.
|
- Android # Build an Android apk.
|
||||||
- StandaloneWindows64 # Build a Windows 64-bit standalone.
|
- StandaloneWindows64 # Build a Windows 64-bit standalone.
|
||||||
- WSAPlayer # Build a UWP App
|
- WSAPlayer # Build a UWP App
|
||||||
- tvOS # Build an Apple TV XCode project
|
- tvOS # Build an Apple TV XCode project
|
||||||
|
enableGpu:
|
||||||
|
- false
|
||||||
|
include:
|
||||||
|
# Additionally test enableGpu build for a standalone windows target
|
||||||
|
- projectPath: test-project
|
||||||
|
unityVersion: 2023.2.2f1
|
||||||
|
targetPlatform: StandaloneWindows64
|
||||||
|
enableGpu: true
|
||||||
|
- unityVersion: 6000.0.36f1
|
||||||
|
targetPlatform: StandaloneWindows64
|
||||||
|
- unityVersion: 6000.0.36f1
|
||||||
|
targetPlatform: StandaloneWindows64
|
||||||
|
buildProfile: 'Assets/Settings/Build Profiles/Sample Windows Build Profile.asset'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
###########################
|
###########################
|
||||||
# Checkout #
|
# Checkout #
|
||||||
@@ -39,7 +51,7 @@ jobs:
|
|||||||
###########################
|
###########################
|
||||||
# Cache #
|
# Cache #
|
||||||
###########################
|
###########################
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ matrix.projectPath }}/Library
|
path: ${{ matrix.projectPath }}/Library
|
||||||
key: Library-${{ matrix.projectPath }}-windows-${{ matrix.targetPlatform }}
|
key: Library-${{ matrix.projectPath }}-windows-${{ matrix.targetPlatform }}
|
||||||
@@ -66,10 +78,14 @@ jobs:
|
|||||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||||
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
||||||
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||||
with:
|
with:
|
||||||
|
buildName: 'GameCI Test Build'
|
||||||
projectPath: ${{ matrix.projectPath }}
|
projectPath: ${{ matrix.projectPath }}
|
||||||
unityVersion: ${{ matrix.unityVersion }}
|
unityVersion: ${{ matrix.unityVersion }}
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
targetPlatform: ${{ matrix.targetPlatform }}
|
||||||
|
buildProfile: ${{ matrix.buildProfile }}
|
||||||
|
enableGpu: ${{ matrix.enableGpu }}
|
||||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
||||||
allowDirtyBuild: true
|
allowDirtyBuild: true
|
||||||
# We use dirty build because we are replacing the default project settings file above
|
# We use dirty build because we are replacing the default project settings file above
|
||||||
@@ -89,10 +105,13 @@ jobs:
|
|||||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||||
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
||||||
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||||
with:
|
with:
|
||||||
|
buildName: 'GameCI Test Build'
|
||||||
projectPath: ${{ matrix.projectPath }}
|
projectPath: ${{ matrix.projectPath }}
|
||||||
unityVersion: ${{ matrix.unityVersion }}
|
unityVersion: ${{ matrix.unityVersion }}
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
targetPlatform: ${{ matrix.targetPlatform }}
|
||||||
|
enableGpu: ${{ matrix.enableGpu }}
|
||||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
||||||
allowDirtyBuild: true
|
allowDirtyBuild: true
|
||||||
# We use dirty build because we are replacing the default project settings file above
|
# We use dirty build because we are replacing the default project settings file above
|
||||||
@@ -111,10 +130,13 @@ jobs:
|
|||||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||||
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
||||||
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||||
with:
|
with:
|
||||||
|
buildName: 'GameCI Test Build'
|
||||||
projectPath: ${{ matrix.projectPath }}
|
projectPath: ${{ matrix.projectPath }}
|
||||||
unityVersion: ${{ matrix.unityVersion }}
|
unityVersion: ${{ matrix.unityVersion }}
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
targetPlatform: ${{ matrix.targetPlatform }}
|
||||||
|
enableGpu: ${{ matrix.enableGpu }}
|
||||||
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
|
||||||
allowDirtyBuild: true
|
allowDirtyBuild: true
|
||||||
# We use dirty build because we are replacing the default project settings file above
|
# We use dirty build because we are replacing the default project settings file above
|
||||||
@@ -122,8 +144,8 @@ jobs:
|
|||||||
###########################
|
###########################
|
||||||
# Upload #
|
# Upload #
|
||||||
###########################
|
###########################
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Build Windows (${{ matrix.unityVersion }})
|
name: Build ${{ matrix.targetPlatform }} on Windows (${{ matrix.unityVersion }})${{ matrix.enableGpu && ' With GPU' || '' }}${{ matrix.buildProfile && ' With Build Profile' || '' }}
|
||||||
path: build
|
path: build
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
|||||||
37
.github/workflows/cleanup.yml
vendored
37
.github/workflows/cleanup.yml
vendored
@@ -1,37 +0,0 @@
|
|||||||
name: Cleanup (cron)
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '30 10 * * SUN' # every sunday at 10:30
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deleteArtifacts:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Delete old artifacts
|
|
||||||
uses: kolpav/purge-artifacts-action@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
expire-in: 21 days
|
|
||||||
cleanupCloudRunner:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
if: github.event.event_type != 'pull_request_target'
|
|
||||||
with:
|
|
||||||
lfs: true
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '18'
|
|
||||||
- run: yarn
|
|
||||||
- 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 list-resources
|
|
||||||
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
|
|
||||||
190
.github/workflows/cloud-runner-ci-pipeline.yml
vendored
190
.github/workflows/cloud-runner-ci-pipeline.yml
vendored
@@ -1,190 +0,0 @@
|
|||||||
name: Cloud Runner CI Pipeline
|
|
||||||
|
|
||||||
on:
|
|
||||||
push: { branches: [cloud-runner-develop, cloud-runner-preview, main] }
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
contents: read
|
|
||||||
actions: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
GKE_ZONE: 'us-central1'
|
|
||||||
GKE_REGION: 'us-central1'
|
|
||||||
GKE_PROJECT: 'unitykubernetesbuilder'
|
|
||||||
GKE_CLUSTER: 'game-ci-github-pipelines'
|
|
||||||
GCP_LOGGING: true
|
|
||||||
GCP_PROJECT: unitykubernetesbuilder
|
|
||||||
GCP_LOG_FILE: ${{ github.workspace }}/cloud-runner-logs.txt
|
|
||||||
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_STACK_NAME: game-ci-team-pipelines
|
|
||||||
CLOUD_RUNNER_BRANCH: ${{ github.ref }}
|
|
||||||
DEBUG: true
|
|
||||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
||||||
PROJECT_PATH: test-project
|
|
||||||
UNITY_VERSION: 2019.3.15f1
|
|
||||||
USE_IL2CPP: false
|
|
||||||
USE_GKE_GCLOUD_AUTH_PLUGIN: true
|
|
||||||
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
smokeTests:
|
|
||||||
name: Smoke Tests
|
|
||||||
if: github.event.event_type != 'pull_request_target'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
test:
|
|
||||||
#- 'cloud-runner-async-workflow'
|
|
||||||
- 'cloud-runner-caching'
|
|
||||||
# - 'cloud-runner-end2end-caching'
|
|
||||||
# - 'cloud-runner-end2end-retaining'
|
|
||||||
- 'cloud-runner-environment'
|
|
||||||
- 'cloud-runner-hooks'
|
|
||||||
- 'cloud-runner-local-persistence'
|
|
||||||
- 'cloud-runner-locking-core'
|
|
||||||
- 'cloud-runner-locking-get-locked'
|
|
||||||
providerStrategy:
|
|
||||||
#- aws
|
|
||||||
- local-docker
|
|
||||||
#- k8s
|
|
||||||
steps:
|
|
||||||
- name: Checkout (default)
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
lfs: false
|
|
||||||
- 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
|
|
||||||
- uses: google-github-actions/auth@v1
|
|
||||||
if: matrix.providerStrategy == 'k8s'
|
|
||||||
with:
|
|
||||||
credentials_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}
|
|
||||||
- name: 'Set up Cloud SDK'
|
|
||||||
if: matrix.providerStrategy == 'k8s'
|
|
||||||
uses: 'google-github-actions/setup-gcloud@v1.1.0'
|
|
||||||
- name: Get GKE cluster credentials
|
|
||||||
if: matrix.providerStrategy == 'k8s'
|
|
||||||
run: |
|
|
||||||
export USE_GKE_GCLOUD_AUTH_PLUGIN=True
|
|
||||||
gcloud components install gke-gcloud-auth-plugin
|
|
||||||
gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $GKE_PROJECT
|
|
||||||
- run: yarn
|
|
||||||
- run: yarn run test "${{ matrix.test }}" --detectOpenHandles --forceExit --runInBand
|
|
||||||
timeout-minutes: 35
|
|
||||||
env:
|
|
||||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
||||||
PROJECT_PATH: test-project
|
|
||||||
TARGET_PLATFORM: StandaloneWindows64
|
|
||||||
cloudRunnerTests: true
|
|
||||||
versioning: None
|
|
||||||
CLOUD_RUNNER_CLUSTER: ${{ matrix.providerStrategy }}
|
|
||||||
tests:
|
|
||||||
# needs:
|
|
||||||
# - smokeTests
|
|
||||||
# - buildTargetTests
|
|
||||||
name: Integration Tests
|
|
||||||
if: github.event.event_type != 'pull_request_target'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
providerStrategy:
|
|
||||||
- aws
|
|
||||||
- local-docker
|
|
||||||
- k8s
|
|
||||||
test:
|
|
||||||
- 'cloud-runner-async-workflow'
|
|
||||||
#- 'cloud-runner-caching'
|
|
||||||
- 'cloud-runner-end2end-locking'
|
|
||||||
- 'cloud-runner-end2end-caching'
|
|
||||||
- 'cloud-runner-end2end-retaining'
|
|
||||||
- 'cloud-runner-environment'
|
|
||||||
#- 'cloud-runner-hooks'
|
|
||||||
- 'cloud-runner-s3-steps'
|
|
||||||
#- 'cloud-runner-local-persistence'
|
|
||||||
#- 'cloud-runner-locking-core'
|
|
||||||
#- 'cloud-runner-locking-get-locked'
|
|
||||||
steps:
|
|
||||||
- name: Checkout (default)
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
lfs: false
|
|
||||||
- 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
|
|
||||||
- uses: google-github-actions/auth@v1
|
|
||||||
if: matrix.providerStrategy == 'k8s'
|
|
||||||
with:
|
|
||||||
credentials_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}
|
|
||||||
- name: 'Set up Cloud SDK'
|
|
||||||
if: matrix.providerStrategy == 'k8s'
|
|
||||||
uses: 'google-github-actions/setup-gcloud@v1.1.0'
|
|
||||||
- name: Get GKE cluster credentials
|
|
||||||
if: matrix.providerStrategy == 'k8s'
|
|
||||||
run: |
|
|
||||||
export USE_GKE_GCLOUD_AUTH_PLUGIN=True
|
|
||||||
gcloud components install gke-gcloud-auth-plugin
|
|
||||||
gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $GKE_PROJECT
|
|
||||||
- run: yarn
|
|
||||||
- run: yarn run test "${{ matrix.test }}" --detectOpenHandles --forceExit --runInBand
|
|
||||||
timeout-minutes: 60
|
|
||||||
env:
|
|
||||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
||||||
PROJECT_PATH: test-project
|
|
||||||
TARGET_PLATFORM: StandaloneWindows64
|
|
||||||
cloudRunnerTests: true
|
|
||||||
versioning: None
|
|
||||||
PROVIDER_STRATEGY: ${{ matrix.providerStrategy }}
|
|
||||||
buildTargetTests:
|
|
||||||
name: Local Build Target Tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
providerStrategy:
|
|
||||||
#- aws
|
|
||||||
- local-docker
|
|
||||||
#- k8s
|
|
||||||
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.
|
|
||||||
steps:
|
|
||||||
- name: Checkout (default)
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
lfs: false
|
|
||||||
- run: yarn
|
|
||||||
- uses: ./
|
|
||||||
id: unity-build
|
|
||||||
timeout-minutes: 30
|
|
||||||
env:
|
|
||||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
||||||
with:
|
|
||||||
cloudRunnerTests: true
|
|
||||||
versioning: None
|
|
||||||
targetPlatform: ${{ matrix.targetPlatform }}
|
|
||||||
providerStrategy: ${{ matrix.providerStrategy }}
|
|
||||||
- run: |
|
|
||||||
cp ./cloud-runner-cache/cache/${{ steps.unity-build.outputs.CACHE_KEY }}/build/${{ steps.unity-build.outputs.BUILD_ARTIFACT }} ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.providerStrategy }} Build (${{ matrix.targetPlatform }})
|
|
||||||
path: ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}
|
|
||||||
retention-days: 14
|
|
||||||
14
.github/workflows/integrity-check.yml
vendored
14
.github/workflows/integrity-check.yml
vendored
@@ -4,6 +4,11 @@ on:
|
|||||||
push: { branches: [main] }
|
push: { branches: [main] }
|
||||||
pull_request: {}
|
pull_request: {}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
checks: write
|
||||||
|
statuses: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CODECOV_TOKEN: '2f2eb890-30e2-4724-83eb-7633832cf0de'
|
CODECOV_TOKEN: '2f2eb890-30e2-4724-83eb-7633832cf0de'
|
||||||
|
|
||||||
@@ -22,7 +27,12 @@ jobs:
|
|||||||
node-version: '18'
|
node-version: '18'
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
- run: yarn test --coverage
|
- run: yarn test:ci --coverage
|
||||||
- run: bash <(curl -s https://codecov.io/bash)
|
- run: bash <(curl -s https://codecov.io/bash)
|
||||||
- run: yarn build || { echo "build command should always succeed" ; exit 61; }
|
- run: yarn build || { echo "build command should always succeed" ; exit 61; }
|
||||||
# - run: yarn build --quiet && git diff --quiet dist || { echo "dist should be auto generated" ; git diff dist ; exit 62; }
|
# - run: yarn build --quiet && git diff --quiet dist || { echo "dist should be auto generated" ; git diff dist ; exit 62; }
|
||||||
|
|
||||||
|
orchestrator:
|
||||||
|
name: Orchestrator Integrity
|
||||||
|
uses: ./.github/workflows/orchestrator-integrity.yml
|
||||||
|
secrets: inherit
|
||||||
|
|||||||
@@ -18,15 +18,16 @@ env:
|
|||||||
GKE_CLUSTER: 'game-ci-github-pipelines'
|
GKE_CLUSTER: 'game-ci-github-pipelines'
|
||||||
GCP_LOGGING: true
|
GCP_LOGGING: true
|
||||||
GCP_PROJECT: unitykubernetesbuilder
|
GCP_PROJECT: unitykubernetesbuilder
|
||||||
GCP_LOG_FILE: ${{ github.workspace }}/cloud-runner-logs.txt
|
GCP_LOG_FILE: ${{ github.workspace }}/orchestrator-logs.txt
|
||||||
AWS_REGION: eu-west-2
|
# Commented out: Using LocalStack tests instead of real AWS
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
# AWS_REGION: eu-west-2
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
AWS_DEFAULT_REGION: eu-west-2
|
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_STACK_NAME: game-ci-github-pipelines
|
# AWS_DEFAULT_REGION: eu-west-2
|
||||||
CLOUD_RUNNER_BRANCH: ${{ github.ref }}
|
# AWS_STACK_NAME: game-ci-github-pipelines
|
||||||
CLOUD_RUNNER_DEBUG: true
|
ORCHESTRATOR_BRANCH: ${{ github.ref }}
|
||||||
CLOUD_RUNNER_DEBUG_TREE: true
|
ORCHESTRATOR_DEBUG: true
|
||||||
|
ORCHESTRATOR_DEBUG_TREE: true
|
||||||
DEBUG: true
|
DEBUG: true
|
||||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||||
PROJECT_PATH: test-project
|
PROJECT_PATH: test-project
|
||||||
@@ -46,13 +47,14 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GIT_PRIVATE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GIT_PRIVATE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TARGET_PLATFORM: StandaloneWindows64
|
TARGET_PLATFORM: StandaloneWindows64
|
||||||
cloudRunnerTests: true
|
orchestratorTests: true
|
||||||
versioning: None
|
versioning: None
|
||||||
CLOUD_RUNNER_CLUSTER: local-docker
|
ORCHESTRATOR_CLUSTER: local-docker
|
||||||
AWS_STACK_NAME: game-ci-github-pipelines
|
# Commented out: Using LocalStack tests instead of real AWS
|
||||||
|
# AWS_STACK_NAME: game-ci-github-pipelines
|
||||||
CHECKS_UPDATE: ${{ github.event.inputs.checksObject }}
|
CHECKS_UPDATE: ${{ github.event.inputs.checksObject }}
|
||||||
run: |
|
run: |
|
||||||
git clone -b cloud-runner-develop https://github.com/game-ci/unity-builder
|
git clone -b main https://github.com/game-ci/unity-builder
|
||||||
cd unity-builder
|
cd unity-builder
|
||||||
yarn
|
yarn
|
||||||
ls
|
ls
|
||||||
1109
.github/workflows/orchestrator-integrity.yml
vendored
Normal file
1109
.github/workflows/orchestrator-integrity.yml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ lib/
|
|||||||
.vsconfig
|
.vsconfig
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
.orig
|
.orig
|
||||||
|
$LOG_FILE
|
||||||
|
temp/
|
||||||
|
|||||||
82
action.yml
82
action.yml
@@ -18,7 +18,11 @@ inputs:
|
|||||||
projectPath:
|
projectPath:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description: 'Relative path to the project to be built.'
|
description: 'Path to the project to be built, relative to the repository root.'
|
||||||
|
buildProfile:
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
description: 'Path to the build profile to activate, relative to the project root.'
|
||||||
buildName:
|
buildName:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
@@ -35,6 +39,10 @@ inputs:
|
|||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description: 'Suppresses `-quit`. Exit your build method using `EditorApplication.Exit(0)` instead.'
|
description: 'Suppresses `-quit`. Exit your build method using `EditorApplication.Exit(0)` instead.'
|
||||||
|
enableGpu:
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
description: 'Launches unity without specifying `-nographics`.'
|
||||||
customParameters:
|
customParameters:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
@@ -96,11 +104,17 @@ inputs:
|
|||||||
gitPrivateToken:
|
gitPrivateToken:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description: '[CloudRunner] Github private token to pull from github'
|
description: '[Orchestrator] Github private token to pull from github'
|
||||||
|
gitAuthMode:
|
||||||
|
required: false
|
||||||
|
default: 'header'
|
||||||
|
description:
|
||||||
|
'[Orchestrator] How git authentication is configured. "header" (default) uses http.extraHeader so the token
|
||||||
|
never appears in clone URLs or git config. "url" embeds the token in clone URLs (legacy behavior).'
|
||||||
githubOwner:
|
githubOwner:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description: '[CloudRunner] GitHub owner name or organization/team name'
|
description: '[Orchestrator] GitHub owner name or organization/team name'
|
||||||
runAsHostUser:
|
runAsHostUser:
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'false'
|
||||||
@@ -141,97 +155,101 @@ inputs:
|
|||||||
allowDirtyBuild:
|
allowDirtyBuild:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description: '[CloudRunner] Allows the branch of the build to be dirty, and still generate the build.'
|
description: '[Orchestrator] Allows the branch of the build to be dirty, and still generate the build.'
|
||||||
postBuildSteps:
|
postBuildSteps:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] run a post build job in yaml format with the keys image, secrets (name, value object array),
|
'[Orchestrator] run a post build job in yaml format with the keys image, secrets (name, value object array),
|
||||||
command string'
|
command string'
|
||||||
preBuildSteps:
|
preBuildSteps:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Run a pre build job after the repository setup but before the build job (in yaml format with the
|
'[Orchestrator] Run a pre build job after the repository setup but before the build job (in yaml format with the
|
||||||
keys image, secrets (name, value object array), command line string)'
|
keys image, secrets (name, value object array), command line string)'
|
||||||
containerHookFiles:
|
containerHookFiles:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Specify the names (by file name) of custom steps to run before or after cloud runner jobs, must
|
'[Orchestrator] Specify the names (by file name) of custom steps to run before or after orchestrator jobs, must
|
||||||
match a yaml step file inside your repo in the folder .game-ci/steps/'
|
match a yaml step file inside your repo in the folder .game-ci/steps/'
|
||||||
customHookFiles:
|
customHookFiles:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Specify the names (by file name) of custom hooks to run before or after cloud runner jobs, must
|
'[Orchestrator] Specify the names (by file name) of custom hooks to run before or after orchestrator jobs, must
|
||||||
match a yaml step file inside your repo in the folder .game-ci/hooks/'
|
match a yaml step file inside your repo in the folder .game-ci/hooks/'
|
||||||
customCommandHooks:
|
customCommandHooks:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description: '[CloudRunner] Specify custom commands and trigger hooks (injects commands into jobs)'
|
description: '[Orchestrator] Specify custom commands and trigger hooks (injects commands into jobs)'
|
||||||
customJob:
|
customJob:
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Run a custom job instead of the standard build automation for cloud runner (in yaml format with the
|
'[Orchestrator] Run a custom job instead of the standard build automation for orchestrator (in yaml format with the
|
||||||
keys image, secrets (name, value object array), command line string)'
|
keys image, secrets (name, value object array), command line string)'
|
||||||
awsStackName:
|
awsStackName:
|
||||||
default: 'game-ci'
|
default: 'game-ci'
|
||||||
required: false
|
required: false
|
||||||
description: '[CloudRunner] The Cloud Formation stack name that must be setup before using this option.'
|
description: '[Orchestrator] The Cloud Formation stack name that must be setup before using this option.'
|
||||||
providerStrategy:
|
providerStrategy:
|
||||||
default: 'local'
|
default: 'local'
|
||||||
required: false
|
required: false
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Either local, k8s or aws can be used to run builds on a remote cluster. Additional parameters must
|
'[Orchestrator] Either local, k8s or aws can be used to run builds on a remote cluster. Additional parameters must
|
||||||
be configured.'
|
be configured.'
|
||||||
cloudRunnerCpu:
|
resourceTracking:
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
|
description: '[Orchestrator] Enable resource tracking logs for disk usage and allocation summaries.'
|
||||||
|
containerCpu:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description: '[CloudRunner] Amount of CPU time to assign the remote build container'
|
description: '[Orchestrator] Amount of CPU time to assign the remote build container'
|
||||||
cloudRunnerMemory:
|
containerMemory:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description: '[CloudRunner] Amount of memory to assign the remote build container'
|
description: '[Orchestrator] Amount of memory to assign the remote build container'
|
||||||
readInputFromOverrideList:
|
readInputFromOverrideList:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description: '[CloudRunner] Comma separated list of input value names to read from "input override command"'
|
description: '[Orchestrator] Comma separated list of input value names to read from "input override command"'
|
||||||
readInputOverrideCommand:
|
readInputOverrideCommand:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Extend game ci by specifying a command to execute to pull input from external source e.g cloud
|
'[Orchestrator] Extend game ci by specifying a command to execute to pull input from external source e.g cloud
|
||||||
provider secret managers'
|
provider secret managers'
|
||||||
kubeConfig:
|
kubeConfig:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Supply a base64 encoded kubernetes config to run builds on kubernetes and stream logs until
|
'[Orchestrator] Supply a base64 encoded kubernetes config to run builds on kubernetes and stream logs until
|
||||||
completion.'
|
completion.'
|
||||||
kubeVolume:
|
kubeVolume:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description: '[CloudRunner] Supply a Persistent Volume Claim name to use for the Unity build.'
|
description: '[Orchestrator] Supply a Persistent Volume Claim name to use for the Unity build.'
|
||||||
kubeStorageClass:
|
kubeStorageClass:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Kubernetes storage class to use for cloud runner jobs, leave empty to install rook cluster.'
|
'[Orchestrator] Kubernetes storage class to use for orchestrator jobs, leave empty to install rook cluster.'
|
||||||
kubeVolumeSize:
|
kubeVolumeSize:
|
||||||
default: '5Gi'
|
default: '5Gi'
|
||||||
required: false
|
required: false
|
||||||
description: '[CloudRunner] Amount of disc space to assign the Kubernetes Persistent Volume'
|
description: '[Orchestrator] Amount of disc space to assign the Kubernetes Persistent Volume'
|
||||||
cacheKey:
|
cacheKey:
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
description: '[CloudRunner] Cache key to indicate bucket for cache'
|
description: '[Orchestrator] Cache key to indicate bucket for cache'
|
||||||
watchToEnd:
|
watchToEnd:
|
||||||
default: 'true'
|
default: 'true'
|
||||||
required: false
|
required: false
|
||||||
description:
|
description:
|
||||||
'[CloudRunner] Whether or not to watch the build to the end. Can be used for especially long running jobs e.g
|
'[Orchestrator] Whether or not to watch the build to the end. Can be used for especially long running jobs e.g
|
||||||
imports or self-hosted ephemeral runners.'
|
imports or self-hosted ephemeral runners.'
|
||||||
cacheUnityInstallationOnMac:
|
cacheUnityInstallationOnMac:
|
||||||
default: 'false'
|
default: 'false'
|
||||||
@@ -253,6 +271,20 @@ inputs:
|
|||||||
description:
|
description:
|
||||||
'The path to mount the workspace inside the docker container. For windows, leave out the drive letter. For example
|
'The path to mount the workspace inside the docker container. For windows, leave out the drive letter. For example
|
||||||
c:/github/workspace should be defined as /github/workspace'
|
c:/github/workspace should be defined as /github/workspace'
|
||||||
|
skipActivation:
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
|
description: 'Skip the activation/deactivation of Unity. This assumes Unity is already activated.'
|
||||||
|
cloneDepth:
|
||||||
|
default: '50'
|
||||||
|
required: false
|
||||||
|
description: '[Orchestrator] Specifies the depth of the git clone for the repository. Use 0 for full clone.'
|
||||||
|
orchestratorRepoName:
|
||||||
|
default: 'game-ci/unity-builder'
|
||||||
|
required: false
|
||||||
|
description:
|
||||||
|
'[Orchestrator] Specifies the repo for the unity builder. Useful if you forked the repo for testing, features, or
|
||||||
|
fixes.'
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
volume:
|
volume:
|
||||||
@@ -270,5 +302,5 @@ branding:
|
|||||||
icon: 'box'
|
icon: 'box'
|
||||||
color: 'gray-dark'
|
color: 'gray-dark'
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node20'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|||||||
BIN
dist/BlankProject/Packages/.DS_Store
vendored
BIN
dist/BlankProject/Packages/.DS_Store
vendored
Binary file not shown.
@@ -6,6 +6,9 @@ using UnityBuilderAction.Reporting;
|
|||||||
using UnityBuilderAction.Versioning;
|
using UnityBuilderAction.Versioning;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Build.Reporting;
|
using UnityEditor.Build.Reporting;
|
||||||
|
#if UNITY_6000_0_OR_NEWER
|
||||||
|
using UnityEditor.Build.Profile;
|
||||||
|
#endif
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace UnityBuilderAction
|
namespace UnityBuilderAction
|
||||||
@@ -17,47 +20,9 @@ namespace UnityBuilderAction
|
|||||||
// Gather values from args
|
// Gather values from args
|
||||||
var options = ArgumentsParser.GetValidatedOptions();
|
var options = ArgumentsParser.GetValidatedOptions();
|
||||||
|
|
||||||
// Gather values from project
|
|
||||||
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
|
|
||||||
|
|
||||||
// Get all buildOptions from options
|
|
||||||
BuildOptions buildOptions = BuildOptions.None;
|
|
||||||
foreach (string buildOptionString in Enum.GetNames(typeof(BuildOptions))) {
|
|
||||||
if (options.ContainsKey(buildOptionString)) {
|
|
||||||
BuildOptions buildOptionEnum = (BuildOptions) Enum.Parse(typeof(BuildOptions), buildOptionString);
|
|
||||||
buildOptions |= buildOptionEnum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if UNITY_2021_2_OR_NEWER
|
|
||||||
// Determine subtarget
|
|
||||||
StandaloneBuildSubtarget buildSubtarget;
|
|
||||||
if (!options.TryGetValue("standaloneBuildSubtarget", out var subtargetValue) || !Enum.TryParse(subtargetValue, out buildSubtarget)) {
|
|
||||||
buildSubtarget = default;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Define BuildPlayer Options
|
|
||||||
var buildPlayerOptions = new BuildPlayerOptions {
|
|
||||||
scenes = scenes,
|
|
||||||
locationPathName = options["customBuildPath"],
|
|
||||||
target = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]),
|
|
||||||
options = buildOptions,
|
|
||||||
#if UNITY_2021_2_OR_NEWER
|
|
||||||
subtarget = (int) buildSubtarget
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set version for this build
|
// Set version for this build
|
||||||
VersionApplicator.SetVersion(options["buildVersion"]);
|
VersionApplicator.SetVersion(options["buildVersion"]);
|
||||||
|
|
||||||
// Apply Android settings
|
|
||||||
if (buildPlayerOptions.target == BuildTarget.Android)
|
|
||||||
{
|
|
||||||
VersionApplicator.SetAndroidVersionCode(options["androidVersionCode"]);
|
|
||||||
AndroidSettings.Apply(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute default AddressableAsset content build, if the package is installed.
|
// Execute default AddressableAsset content build, if the package is installed.
|
||||||
// Version defines would be the best solution here, but Unity 2018 doesn't support that,
|
// Version defines would be the best solution here, but Unity 2018 doesn't support that,
|
||||||
// so we fall back to using reflection instead.
|
// so we fall back to using reflection instead.
|
||||||
@@ -74,10 +39,85 @@ namespace UnityBuilderAction
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogError($"Failed to run default addressables build:\n{e}");
|
Debug.LogError("Failed to run default addressables build:\n" + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all buildOptions from options
|
||||||
|
BuildOptions buildOptions = BuildOptions.None;
|
||||||
|
foreach (string buildOptionString in Enum.GetNames(typeof(BuildOptions))) {
|
||||||
|
if (options.ContainsKey(buildOptionString)) {
|
||||||
|
BuildOptions buildOptionEnum = (BuildOptions) Enum.Parse(typeof(BuildOptions), buildOptionString);
|
||||||
|
buildOptions |= buildOptionEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depending on whether the build is using a build profile, `buildPlayerOptions` will an instance
|
||||||
|
// of either `UnityEditor.BuildPlayerOptions` or `UnityEditor.BuildPlayerWithProfileOptions`
|
||||||
|
dynamic buildPlayerOptions;
|
||||||
|
|
||||||
|
if (options.TryGetValue("activeBuildProfile", out var buildProfilePath)) {
|
||||||
|
if (string.IsNullOrEmpty(buildProfilePath)) {
|
||||||
|
throw new Exception("`-activeBuildProfile` is set but with an empty value; this shouldn't happen");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_6000_0_OR_NEWER
|
||||||
|
// Load build profile from Assets folder
|
||||||
|
var buildProfile = AssetDatabase.LoadAssetAtPath<BuildProfile>(buildProfilePath)
|
||||||
|
?? throw new Exception("Build profile file not found at path: " + buildProfilePath);
|
||||||
|
|
||||||
|
// no need to set active profile, as already set by `-activeBuildProfile` CLI argument
|
||||||
|
// BuildProfile.SetActiveBuildProfile(buildProfile);
|
||||||
|
Debug.Log($"build profile: {buildProfile.name}");
|
||||||
|
|
||||||
|
// Define BuildPlayerWithProfileOptions
|
||||||
|
buildPlayerOptions = new BuildPlayerWithProfileOptions {
|
||||||
|
buildProfile = buildProfile,
|
||||||
|
locationPathName = options["customBuildPath"],
|
||||||
|
options = buildOptions,
|
||||||
|
};
|
||||||
|
#else // UNITY_6000_0_OR_NEWER
|
||||||
|
throw new Exception("Build profiles are not supported by this version of Unity (" + Application.unityVersion +")");
|
||||||
|
#endif // UNITY_6000_0_OR_NEWER
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
#if BUILD_PROFILE_LOADED
|
||||||
|
throw new Exception("Build profile's define symbol present; shouldn't happen");
|
||||||
|
#endif // BUILD_PROFILE_LOADED
|
||||||
|
|
||||||
|
// Gather values from project
|
||||||
|
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
|
||||||
|
|
||||||
|
#if UNITY_2021_2_OR_NEWER
|
||||||
|
// Determine subtarget
|
||||||
|
StandaloneBuildSubtarget buildSubtarget;
|
||||||
|
if (!options.TryGetValue("standaloneBuildSubtarget", out var subtargetValue) || !Enum.TryParse(subtargetValue, out buildSubtarget)) {
|
||||||
|
buildSubtarget = default;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BuildTarget buildTarget = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]);
|
||||||
|
|
||||||
|
// Define BuildPlayerOptions
|
||||||
|
buildPlayerOptions = new BuildPlayerOptions {
|
||||||
|
scenes = scenes,
|
||||||
|
locationPathName = options["customBuildPath"],
|
||||||
|
target = buildTarget,
|
||||||
|
options = buildOptions,
|
||||||
|
#if UNITY_2021_2_OR_NEWER
|
||||||
|
subtarget = (int) buildSubtarget
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply Android settings
|
||||||
|
if (buildTarget == BuildTarget.Android) {
|
||||||
|
VersionApplicator.SetAndroidVersionCode(options["androidVersionCode"]);
|
||||||
|
AndroidSettings.Apply(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Perform build
|
// Perform build
|
||||||
BuildReport buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions);
|
BuildReport buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions);
|
||||||
|
|
||||||
|
|||||||
@@ -56,17 +56,17 @@ namespace UnityBuilderAction.Input
|
|||||||
case "androidStudioProject":
|
case "androidStudioProject":
|
||||||
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
|
||||||
if (buildAppBundle != null)
|
if (buildAppBundle != null)
|
||||||
buildAppBundle.SetValue(null, false);
|
buildAppBundle.SetValue(null, false, null);
|
||||||
break;
|
break;
|
||||||
case "androidAppBundle":
|
case "androidAppBundle":
|
||||||
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
||||||
if (buildAppBundle != null)
|
if (buildAppBundle != null)
|
||||||
buildAppBundle.SetValue(null, true);
|
buildAppBundle.SetValue(null, true, null);
|
||||||
break;
|
break;
|
||||||
case "androidPackage":
|
case "androidPackage":
|
||||||
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
||||||
if (buildAppBundle != null)
|
if (buildAppBundle != null)
|
||||||
buildAppBundle.SetValue(null, false);
|
buildAppBundle.SetValue(null, false, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,20 @@ namespace UnityBuilderAction.Input
|
|||||||
string symbolType;
|
string symbolType;
|
||||||
if (options.TryGetValue("androidSymbolType", out symbolType) && !string.IsNullOrEmpty(symbolType))
|
if (options.TryGetValue("androidSymbolType", out symbolType) && !string.IsNullOrEmpty(symbolType))
|
||||||
{
|
{
|
||||||
#if UNITY_2021_1_OR_NEWER
|
#if UNITY_6000_0_OR_NEWER
|
||||||
|
switch (symbolType)
|
||||||
|
{
|
||||||
|
case "public":
|
||||||
|
SetDebugSymbols("SymbolTable");
|
||||||
|
break;
|
||||||
|
case "debugging":
|
||||||
|
SetDebugSymbols("Full");
|
||||||
|
break;
|
||||||
|
case "none":
|
||||||
|
SetDebugSymbols("None");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#elif UNITY_2021_1_OR_NEWER
|
||||||
switch (symbolType)
|
switch (symbolType)
|
||||||
{
|
{
|
||||||
case "public":
|
case "public":
|
||||||
@@ -101,5 +114,37 @@ namespace UnityBuilderAction.Input
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_6000_0_OR_NEWER
|
||||||
|
private static void SetDebugSymbols(string enumValueName)
|
||||||
|
{
|
||||||
|
// UnityEditor.Android.UserBuildSettings and Unity.Android.Types.DebugSymbolLevel are part of the Unity Android module.
|
||||||
|
// Reflection is used here to ensure the code works even if the module is not installed.
|
||||||
|
|
||||||
|
var debugSymbolsType = Type.GetType("UnityEditor.Android.UserBuildSettings+DebugSymbols, UnityEditor.Android.Extensions");
|
||||||
|
if (debugSymbolsType == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelProp = debugSymbolsType.GetProperty("level", BindingFlags.Static | BindingFlags.Public);
|
||||||
|
if (levelProp == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enumType = Type.GetType("Unity.Android.Types.DebugSymbolLevel, Unity.Android.Types");
|
||||||
|
if (enumType == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Enum.TryParse(enumType, enumValueName, false , out var enumValue))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
levelProp.SetValue(null, enumValue);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,19 @@ namespace UnityBuilderAction.Input
|
|||||||
EditorApplication.Exit(110);
|
EditorApplication.Exit(110);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_6000_0_OR_NEWER
|
||||||
|
var buildProfileSupport = true;
|
||||||
|
#else
|
||||||
|
var buildProfileSupport = false;
|
||||||
|
#endif // UNITY_6000_0_OR_NEWER
|
||||||
|
|
||||||
|
string buildProfile;
|
||||||
|
if (buildProfileSupport && validatedOptions.TryGetValue("activeBuildProfile", out buildProfile)) {
|
||||||
|
if (validatedOptions.ContainsKey("buildTarget")) {
|
||||||
|
Console.WriteLine("Extra argument -buildTarget");
|
||||||
|
EditorApplication.Exit(122);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
string buildTarget;
|
string buildTarget;
|
||||||
if (!validatedOptions.TryGetValue("buildTarget", out buildTarget)) {
|
if (!validatedOptions.TryGetValue("buildTarget", out buildTarget)) {
|
||||||
Console.WriteLine("Missing argument -buildTarget");
|
Console.WriteLine("Missing argument -buildTarget");
|
||||||
@@ -28,9 +41,10 @@ namespace UnityBuilderAction.Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) {
|
if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) {
|
||||||
Console.WriteLine($"{buildTarget} is not a defined {nameof(BuildTarget)}");
|
Console.WriteLine(buildTarget + " is not a defined " + typeof(BuildTarget).Name);
|
||||||
EditorApplication.Exit(121);
|
EditorApplication.Exit(121);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string customBuildPath;
|
string customBuildPath;
|
||||||
if (!validatedOptions.TryGetValue("customBuildPath", out customBuildPath)) {
|
if (!validatedOptions.TryGetValue("customBuildPath", out customBuildPath)) {
|
||||||
@@ -41,10 +55,10 @@ namespace UnityBuilderAction.Input
|
|||||||
const string defaultCustomBuildName = "TestBuild";
|
const string defaultCustomBuildName = "TestBuild";
|
||||||
string customBuildName;
|
string customBuildName;
|
||||||
if (!validatedOptions.TryGetValue("customBuildName", out customBuildName)) {
|
if (!validatedOptions.TryGetValue("customBuildName", out customBuildName)) {
|
||||||
Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
|
Console.WriteLine("Missing argument -customBuildName, defaulting to" + defaultCustomBuildName);
|
||||||
validatedOptions.Add("customBuildName", defaultCustomBuildName);
|
validatedOptions.Add("customBuildName", defaultCustomBuildName);
|
||||||
} else if (customBuildName == "") {
|
} else if (customBuildName == "") {
|
||||||
Console.WriteLine($"Invalid argument -customBuildName, defaulting to {defaultCustomBuildName}.");
|
Console.WriteLine("Invalid argument -customBuildName, defaulting to" + defaultCustomBuildName);
|
||||||
validatedOptions.Add("customBuildName", defaultCustomBuildName);
|
validatedOptions.Add("customBuildName", defaultCustomBuildName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,11 +71,11 @@ namespace UnityBuilderAction.Input
|
|||||||
string[] args = Environment.GetCommandLineArgs();
|
string[] args = Environment.GetCommandLineArgs();
|
||||||
|
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"{EOL}" +
|
EOL +
|
||||||
$"###########################{EOL}" +
|
"###########################" + EOL +
|
||||||
$"# Parsing settings #{EOL}" +
|
"# Parsing settings #" + EOL +
|
||||||
$"###########################{EOL}" +
|
"###########################" + EOL +
|
||||||
$"{EOL}"
|
EOL
|
||||||
);
|
);
|
||||||
|
|
||||||
// Extract flags with optional values
|
// Extract flags with optional values
|
||||||
@@ -78,7 +92,7 @@ namespace UnityBuilderAction.Input
|
|||||||
string displayValue = secret ? "*HIDDEN*" : "\"" + value + "\"";
|
string displayValue = secret ? "*HIDDEN*" : "\"" + value + "\"";
|
||||||
|
|
||||||
// Assign
|
// Assign
|
||||||
Console.WriteLine($"Found flag \"{flag}\" with value {displayValue}.");
|
Console.WriteLine("Found flag \"" + flag + "\" with value " + displayValue);
|
||||||
providedArguments.Add(flag, value);
|
providedArguments.Add(flag, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace UnityBuilderAction.Reporting
|
|||||||
prefix = "error";
|
prefix = "error";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Console.WriteLine($"{Environment.NewLine}::{prefix} ::{condition}{Environment.NewLine}{stackTrace}");
|
Console.WriteLine(Environment.NewLine + "::" + prefix + "::" + condition + Environment.NewLine + stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ namespace UnityBuilderAction.Reporting
|
|||||||
public static void ReportSummary(BuildSummary summary)
|
public static void ReportSummary(BuildSummary summary)
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"{EOL}" +
|
EOL +
|
||||||
$"###########################{EOL}" +
|
"###########################" + EOL +
|
||||||
$"# Build results #{EOL}" +
|
"# Build results #" + EOL +
|
||||||
$"###########################{EOL}" +
|
"###########################" + EOL +
|
||||||
$"{EOL}" +
|
EOL +
|
||||||
$"Duration: {summary.totalTime.ToString()}{EOL}" +
|
"Duration: " + summary.totalTime.ToString() + EOL +
|
||||||
$"Warnings: {summary.totalWarnings.ToString()}{EOL}" +
|
"Warnings: " + summary.totalWarnings.ToString() + EOL +
|
||||||
$"Errors: {summary.totalErrors.ToString()}{EOL}" +
|
"Errors: " + summary.totalErrors.ToString() + EOL +
|
||||||
$"Size: {summary.totalSize.ToString()} bytes{EOL}" +
|
"Size: " + summary.totalSize.ToString() + " bytes" + EOL +
|
||||||
$"{EOL}"
|
EOL
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ namespace UnityBuilderAction.Versioning
|
|||||||
version = GetSemanticCommitVersion();
|
version = GetSemanticCommitVersion();
|
||||||
Console.WriteLine("Repository has a valid version tag.");
|
Console.WriteLine("Repository has a valid version tag.");
|
||||||
} else {
|
} else {
|
||||||
version = $"0.0.{GetTotalNumberOfCommits()}";
|
version = "0.0." + GetTotalNumberOfCommits();
|
||||||
Console.WriteLine("Repository does not have tags to base the version on.");
|
Console.WriteLine("Repository does not have tags to base the version on.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"Version is {version}");
|
Console.WriteLine("Version is " + version);
|
||||||
|
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|||||||
181326
dist/index.js
generated
vendored
181326
dist/index.js
generated
vendored
File diff suppressed because one or more lines are too long
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
15691
dist/licenses.txt
generated
vendored
15691
dist/licenses.txt
generated
vendored
File diff suppressed because it is too large
Load Diff
36
dist/platforms/mac/entrypoint.sh
vendored
36
dist/platforms/mac/entrypoint.sh
vendored
@@ -1,32 +1,40 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
#
|
#
|
||||||
# Create directories for license activation
|
# Perform Activation
|
||||||
#
|
#
|
||||||
|
|
||||||
UNITY_LICENSE_PATH="/Library/Application Support/Unity"
|
if [ "$SKIP_ACTIVATION" != "true" ]; then
|
||||||
|
UNITY_LICENSE_PATH="/Library/Application Support/Unity"
|
||||||
|
|
||||||
if [ ! -d "$UNITY_LICENSE_PATH" ]; then
|
if [ ! -d "$UNITY_LICENSE_PATH" ]; then
|
||||||
echo "Creating Unity License Directory"
|
echo "Creating Unity License Directory"
|
||||||
sudo mkdir -p "$UNITY_LICENSE_PATH"
|
sudo mkdir -p "$UNITY_LICENSE_PATH"
|
||||||
sudo chmod -R 777 "$UNITY_LICENSE_PATH"
|
sudo chmod -R 777 "$UNITY_LICENSE_PATH"
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
ACTIVATE_LICENSE_PATH="$ACTION_FOLDER/BlankProject"
|
ACTIVATE_LICENSE_PATH="$ACTION_FOLDER/BlankProject"
|
||||||
mkdir -p "$ACTIVATE_LICENSE_PATH"
|
mkdir -p "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
|
source $ACTION_FOLDER/platforms/mac/steps/activate.sh
|
||||||
|
else
|
||||||
|
echo "Skipping activation"
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Run steps
|
# Run Build
|
||||||
#
|
#
|
||||||
source $ACTION_FOLDER/platforms/mac/steps/activate.sh
|
|
||||||
source $ACTION_FOLDER/platforms/mac/steps/build.sh
|
source $ACTION_FOLDER/platforms/mac/steps/build.sh
|
||||||
source $ACTION_FOLDER/platforms/mac/steps/return_license.sh
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Remove license activation directory
|
# License Cleanup
|
||||||
#
|
#
|
||||||
|
|
||||||
rm -r "$ACTIVATE_LICENSE_PATH"
|
if [ "$SKIP_ACTIVATION" != "true" ]; then
|
||||||
|
source $ACTION_FOLDER/platforms/mac/steps/return_license.sh
|
||||||
|
rm -r "$ACTIVATE_LICENSE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Instructions for debugging
|
# Instructions for debugging
|
||||||
|
|||||||
74
dist/platforms/mac/steps/activate.sh
vendored
74
dist/platforms/mac/steps/activate.sh
vendored
@@ -4,21 +4,69 @@
|
|||||||
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
||||||
pushd "$ACTIVATE_LICENSE_PATH"
|
pushd "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
echo "Requesting activation"
|
if [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
|
||||||
|
#
|
||||||
|
# SERIAL LICENSE MODE
|
||||||
|
#
|
||||||
|
# This will activate unity, using the serial activation process.
|
||||||
|
#
|
||||||
|
|
||||||
# Activate license
|
echo "Requesting activation"
|
||||||
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
|
|
||||||
-logFile - \
|
|
||||||
-batchmode \
|
|
||||||
-nographics \
|
|
||||||
-quit \
|
|
||||||
-serial "$UNITY_SERIAL" \
|
|
||||||
-username "$UNITY_EMAIL" \
|
|
||||||
-password "$UNITY_PASSWORD" \
|
|
||||||
-projectPath "$ACTIVATE_LICENSE_PATH"
|
|
||||||
|
|
||||||
# Store the exit code from the verify command
|
# Activate license
|
||||||
UNITY_EXIT_CODE=$?
|
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
|
||||||
|
-logFile - \
|
||||||
|
-batchmode \
|
||||||
|
-nographics \
|
||||||
|
-quit \
|
||||||
|
-serial "$UNITY_SERIAL" \
|
||||||
|
-username "$UNITY_EMAIL" \
|
||||||
|
-password "$UNITY_PASSWORD" \
|
||||||
|
-projectPath "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
|
# Store the exit code from the verify command
|
||||||
|
UNITY_EXIT_CODE=$?
|
||||||
|
|
||||||
|
elif [[ -n "$UNITY_LICENSING_SERVER" ]]; then
|
||||||
|
#
|
||||||
|
# Custom Unity License Server
|
||||||
|
#
|
||||||
|
echo "Adding licensing server config"
|
||||||
|
mkdir -p "$UNITY_LICENSE_PATH/config/"
|
||||||
|
cp "$ACTION_FOLDER/unity-config/services-config.json" "$UNITY_LICENSE_PATH/config/services-config.json"
|
||||||
|
|
||||||
|
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/Frameworks/UnityLicensingClient.app/Contents/MacOS/Unity.Licensing.Client \
|
||||||
|
--acquire-floating > license.txt
|
||||||
|
|
||||||
|
# Store the exit code from the verify command
|
||||||
|
UNITY_EXIT_CODE=$?
|
||||||
|
|
||||||
|
if [ $UNITY_EXIT_CODE -eq 0 ]; then
|
||||||
|
PARSEDFILE=$(grep -oE '\"[^"]*\"' < license.txt | tr -d '"')
|
||||||
|
export FLOATING_LICENSE
|
||||||
|
FLOATING_LICENSE=$(sed -n 2p <<< "$PARSEDFILE")
|
||||||
|
FLOATING_LICENSE_TIMEOUT=$(sed -n 4p <<< "$PARSEDFILE")
|
||||||
|
|
||||||
|
echo "Acquired floating license: \"$FLOATING_LICENSE\" with timeout $FLOATING_LICENSE_TIMEOUT"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
#
|
||||||
|
# NO LICENSE ACTIVATION STRATEGY MATCHED
|
||||||
|
#
|
||||||
|
# This will exit since no activation strategies could be matched.
|
||||||
|
#
|
||||||
|
echo "License activation strategy could not be determined."
|
||||||
|
echo ""
|
||||||
|
echo "Visit https://game.ci/docs/github/activation for more"
|
||||||
|
echo "details on how to set up one of the possible activation strategies."
|
||||||
|
|
||||||
|
echo "::error ::No valid license activation strategy could be determined. Make sure to provide UNITY_EMAIL, UNITY_PASSWORD, and either a UNITY_SERIAL \
|
||||||
|
or UNITY_LICENSE. Otherwise please use UNITY_LICENSING_SERVER. See more info at https://game.ci/docs/github/activation"
|
||||||
|
|
||||||
|
# Immediately exit as no UNITY_EXIT_CODE can be derived.
|
||||||
|
exit 1;
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Display information about the result
|
# Display information about the result
|
||||||
|
|||||||
27
dist/platforms/mac/steps/build.sh
vendored
27
dist/platforms/mac/steps/build.sh
vendored
@@ -19,6 +19,23 @@ echo "Using build name \"$BUILD_NAME\"."
|
|||||||
|
|
||||||
echo "Using build target \"$BUILD_TARGET\"."
|
echo "Using build target \"$BUILD_TARGET\"."
|
||||||
|
|
||||||
|
#
|
||||||
|
# Display the build profile
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ -z "$BUILD_PROFILE" ]; then
|
||||||
|
# User has not provided a build profile
|
||||||
|
#
|
||||||
|
echo "Doing a default \"$BUILD_TARGET\" platform build."
|
||||||
|
#
|
||||||
|
else
|
||||||
|
# User has provided a path to a build profile `.asset` file
|
||||||
|
#
|
||||||
|
echo "Using build profile \"$BUILD_PROFILE\" relative to \"$UNITY_PROJECT_PATH\"."
|
||||||
|
#
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Display build path and file
|
# Display build path and file
|
||||||
#
|
#
|
||||||
@@ -129,16 +146,16 @@ echo ""
|
|||||||
|
|
||||||
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
|
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
|
||||||
-logFile - \
|
-logFile - \
|
||||||
-quit \
|
$( [ "${MANUAL_EXIT}" == "true" ] || echo "-quit" ) \
|
||||||
-batchmode \
|
-batchmode \
|
||||||
-nographics \
|
$( [ "${ENABLE_GPU}" == "true" ] || echo "-nographics" ) \
|
||||||
-username "$UNITY_EMAIL" \
|
|
||||||
-password "$UNITY_PASSWORD" \
|
|
||||||
-customBuildName "$BUILD_NAME" \
|
-customBuildName "$BUILD_NAME" \
|
||||||
-projectPath "$UNITY_PROJECT_PATH" \
|
-projectPath "$UNITY_PROJECT_PATH" \
|
||||||
-buildTarget "$BUILD_TARGET" \
|
$( [ -z "$BUILD_PROFILE" ] && echo "-buildTarget $BUILD_TARGET") \
|
||||||
-customBuildTarget "$BUILD_TARGET" \
|
-customBuildTarget "$BUILD_TARGET" \
|
||||||
-customBuildPath "$CUSTOM_BUILD_PATH" \
|
-customBuildPath "$CUSTOM_BUILD_PATH" \
|
||||||
|
-customBuildProfile "$BUILD_PROFILE" \
|
||||||
|
${BUILD_PROFILE:+-activeBuildProfile} ${BUILD_PROFILE:+"$BUILD_PROFILE"} \
|
||||||
-executeMethod "$BUILD_METHOD" \
|
-executeMethod "$BUILD_METHOD" \
|
||||||
-buildVersion "$VERSION" \
|
-buildVersion "$VERSION" \
|
||||||
-androidVersionCode "$ANDROID_VERSION_CODE" \
|
-androidVersionCode "$ANDROID_VERSION_CODE" \
|
||||||
|
|||||||
32
dist/platforms/mac/steps/return_license.sh
vendored
32
dist/platforms/mac/steps/return_license.sh
vendored
@@ -4,15 +4,29 @@
|
|||||||
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
||||||
pushd "$ACTIVATE_LICENSE_PATH"
|
pushd "$ACTIVATE_LICENSE_PATH"
|
||||||
|
|
||||||
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
|
if [[ -n "$UNITY_LICENSING_SERVER" ]]; then
|
||||||
-logFile - \
|
#
|
||||||
-batchmode \
|
# Return any floating license used.
|
||||||
-nographics \
|
#
|
||||||
-quit \
|
echo "Returning floating license: \"$FLOATING_LICENSE\""
|
||||||
-username "$UNITY_EMAIL" \
|
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/Frameworks/UnityLicensingClient.app/Contents/MacOS/Unity.Licensing.Client \
|
||||||
-password "$UNITY_PASSWORD" \
|
--return-floating "$FLOATING_LICENSE"
|
||||||
-returnlicense \
|
elif [[ -n "$UNITY_SERIAL" ]]; then
|
||||||
-projectPath "$ACTIVATE_LICENSE_PATH"
|
#
|
||||||
|
# SERIAL LICENSE MODE
|
||||||
|
#
|
||||||
|
# This will return the license that is currently in use.
|
||||||
|
#
|
||||||
|
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
|
||||||
|
-logFile - \
|
||||||
|
-batchmode \
|
||||||
|
-nographics \
|
||||||
|
-quit \
|
||||||
|
-username "$UNITY_EMAIL" \
|
||||||
|
-password "$UNITY_PASSWORD" \
|
||||||
|
-returnlicense \
|
||||||
|
-projectPath "$ACTIVATE_LICENSE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
# Return to previous working directory
|
# Return to previous working directory
|
||||||
popd
|
popd
|
||||||
|
|||||||
31
dist/platforms/ubuntu/steps/activate.sh
vendored
31
dist/platforms/ubuntu/steps/activate.sh
vendored
@@ -1,5 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# if blankproject folder doesn't exist create it
|
||||||
|
if [ ! -d "/BlankProject" ]; then
|
||||||
|
mkdir /BlankProject
|
||||||
|
fi
|
||||||
|
# if blankproject folder doesn't exist create it
|
||||||
|
if [ ! -d "/BlankProject/Assets" ]; then
|
||||||
|
mkdir /BlankProject/Assets
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
|
if [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
|
||||||
#
|
#
|
||||||
# SERIAL LICENSE MODE
|
# SERIAL LICENSE MODE
|
||||||
@@ -59,14 +68,18 @@ elif [[ -n "$UNITY_LICENSING_SERVER" ]]; then
|
|||||||
echo "Adding licensing server config"
|
echo "Adding licensing server config"
|
||||||
|
|
||||||
/opt/unity/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client --acquire-floating > license.txt #is this accessible in a env variable?
|
/opt/unity/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client --acquire-floating > license.txt #is this accessible in a env variable?
|
||||||
PARSEDFILE=$(grep -oP '\".*?\"' < license.txt | tr -d '"')
|
|
||||||
export FLOATING_LICENSE
|
|
||||||
FLOATING_LICENSE=$(sed -n 2p <<< "$PARSEDFILE")
|
|
||||||
FLOATING_LICENSE_TIMEOUT=$(sed -n 4p <<< "$PARSEDFILE")
|
|
||||||
|
|
||||||
echo "Acquired floating license: \"$FLOATING_LICENSE\" with timeout $FLOATING_LICENSE_TIMEOUT"
|
|
||||||
# Store the exit code from the verify command
|
# Store the exit code from the verify command
|
||||||
UNITY_EXIT_CODE=$?
|
UNITY_EXIT_CODE=$?
|
||||||
|
|
||||||
|
if [ $UNITY_EXIT_CODE -eq 0 ]; then
|
||||||
|
PARSEDFILE=$(grep -oP '\".*?\"' < license.txt | tr -d '"')
|
||||||
|
export FLOATING_LICENSE
|
||||||
|
FLOATING_LICENSE=$(sed -n 2p <<< "$PARSEDFILE")
|
||||||
|
FLOATING_LICENSE_TIMEOUT=$(sed -n 4p <<< "$PARSEDFILE")
|
||||||
|
|
||||||
|
echo "Acquired floating license: \"$FLOATING_LICENSE\" with timeout $FLOATING_LICENSE_TIMEOUT"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
#
|
#
|
||||||
# NO LICENSE ACTIVATION STRATEGY MATCHED
|
# NO LICENSE ACTIVATION STRATEGY MATCHED
|
||||||
@@ -75,11 +88,12 @@ else
|
|||||||
#
|
#
|
||||||
echo "License activation strategy could not be determined."
|
echo "License activation strategy could not be determined."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Visit https://game.ci/docs/github/getting-started for more"
|
echo "Visit https://game.ci/docs/github/activation for more"
|
||||||
echo "details on how to set up one of the possible activation strategies."
|
echo "details on how to set up one of the possible activation strategies."
|
||||||
|
|
||||||
echo "::error ::No valid license activation strategy could be determined. Make sure to provide UNITY_EMAIL, UNITY_PASSWORD, and either a UNITY_SERIAL \
|
echo "::error ::No valid license activation strategy could be determined. Make sure to provide UNITY_EMAIL, UNITY_PASSWORD, and either a UNITY_SERIAL \
|
||||||
or UNITY_LICENSE. Otherwise please use UNITY_LICENSING_SERVER."
|
or UNITY_LICENSE. Otherwise please use UNITY_LICENSING_SERVER. See more info at https://game.ci/docs/github/activation"
|
||||||
|
|
||||||
# Immediately exit as no UNITY_EXIT_CODE can be derived.
|
# Immediately exit as no UNITY_EXIT_CODE can be derived.
|
||||||
exit 1;
|
exit 1;
|
||||||
|
|
||||||
@@ -98,6 +112,3 @@ else
|
|||||||
echo "::error ::There was an error while trying to activate the Unity license."
|
echo "::error ::There was an error while trying to activate the Unity license."
|
||||||
exit $UNITY_EXIT_CODE
|
exit $UNITY_EXIT_CODE
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Return to previous working directory
|
|
||||||
popd
|
|
||||||
|
|||||||
20
dist/platforms/ubuntu/steps/build.sh
vendored
20
dist/platforms/ubuntu/steps/build.sh
vendored
@@ -19,6 +19,22 @@ echo "Using build name \"$BUILD_NAME\"."
|
|||||||
|
|
||||||
echo "Using build target \"$BUILD_TARGET\"."
|
echo "Using build target \"$BUILD_TARGET\"."
|
||||||
|
|
||||||
|
#
|
||||||
|
# Display the build profile
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ -z "$BUILD_PROFILE" ]; then
|
||||||
|
# User has not provided a build profile
|
||||||
|
#
|
||||||
|
echo "Doing a default \"$BUILD_TARGET\" platform build."
|
||||||
|
#
|
||||||
|
else
|
||||||
|
# User has provided a path to a build profile `.asset` file
|
||||||
|
#
|
||||||
|
echo "Using build profile \"$BUILD_PROFILE\" relative to \"$UNITY_PROJECT_PATH\"."
|
||||||
|
#
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Display build path and file
|
# Display build path and file
|
||||||
#
|
#
|
||||||
@@ -109,9 +125,11 @@ unity-editor \
|
|||||||
$( [ "${MANUAL_EXIT}" == "true" ] || echo "-quit" ) \
|
$( [ "${MANUAL_EXIT}" == "true" ] || echo "-quit" ) \
|
||||||
-customBuildName "$BUILD_NAME" \
|
-customBuildName "$BUILD_NAME" \
|
||||||
-projectPath "$UNITY_PROJECT_PATH" \
|
-projectPath "$UNITY_PROJECT_PATH" \
|
||||||
-buildTarget "$BUILD_TARGET" \
|
$( [ -z "$BUILD_PROFILE" ] && echo "-buildTarget $BUILD_TARGET" ) \
|
||||||
-customBuildTarget "$BUILD_TARGET" \
|
-customBuildTarget "$BUILD_TARGET" \
|
||||||
-customBuildPath "$CUSTOM_BUILD_PATH" \
|
-customBuildPath "$CUSTOM_BUILD_PATH" \
|
||||||
|
-customBuildProfile "$BUILD_PROFILE" \
|
||||||
|
${BUILD_PROFILE:+-activeBuildProfile} ${BUILD_PROFILE:+"$BUILD_PROFILE"} \
|
||||||
-executeMethod "$BUILD_METHOD" \
|
-executeMethod "$BUILD_METHOD" \
|
||||||
-buildVersion "$VERSION" \
|
-buildVersion "$VERSION" \
|
||||||
-androidVersionCode "$ANDROID_VERSION_CODE" \
|
-androidVersionCode "$ANDROID_VERSION_CODE" \
|
||||||
|
|||||||
10
dist/platforms/ubuntu/steps/return_license.sh
vendored
10
dist/platforms/ubuntu/steps/return_license.sh
vendored
@@ -1,11 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Run in ACTIVATE_LICENSE_PATH directory
|
if [[ -n "$UNITY_LICENSING_SERVER" ]]; then
|
||||||
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
|
|
||||||
pushd "$ACTIVATE_LICENSE_PATH"
|
|
||||||
|
|
||||||
|
|
||||||
if [[ -n "$UNITY_LICENSING_SERVER" ]]; then #
|
|
||||||
#
|
#
|
||||||
# Return any floating license used.
|
# Return any floating license used.
|
||||||
#
|
#
|
||||||
@@ -25,6 +20,3 @@ elif [[ -n "$UNITY_SERIAL" ]]; then
|
|||||||
-password "$UNITY_PASSWORD" \
|
-password "$UNITY_PASSWORD" \
|
||||||
-projectPath "/BlankProject"
|
-projectPath "/BlankProject"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Return to previous working directory
|
|
||||||
popd
|
|
||||||
|
|||||||
18
dist/platforms/ubuntu/steps/runsteps.sh
vendored
18
dist/platforms/ubuntu/steps/runsteps.sh
vendored
@@ -5,15 +5,23 @@
|
|||||||
#
|
#
|
||||||
source /steps/set_extra_git_configs.sh
|
source /steps/set_extra_git_configs.sh
|
||||||
source /steps/set_gitcredential.sh
|
source /steps/set_gitcredential.sh
|
||||||
source /steps/activate.sh
|
|
||||||
|
|
||||||
# If we didn't activate successfully, exit with the exit code from the activation step.
|
if [ "$SKIP_ACTIVATION" != "true" ]; then
|
||||||
if [[ $UNITY_EXIT_CODE -ne 0 ]]; then
|
source /steps/activate.sh
|
||||||
exit $UNITY_EXIT_CODE
|
|
||||||
|
# If we didn't activate successfully, exit with the exit code from the activation step.
|
||||||
|
if [[ $UNITY_EXIT_CODE -ne 0 ]]; then
|
||||||
|
exit $UNITY_EXIT_CODE
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Skipping activation"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source /steps/build.sh
|
source /steps/build.sh
|
||||||
source /steps/return_license.sh
|
|
||||||
|
if [ "$SKIP_ACTIVATION" != "true" ]; then
|
||||||
|
source /steps/return_license.sh
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Instructions for debugging
|
# Instructions for debugging
|
||||||
|
|||||||
91
dist/platforms/windows/activate.ps1
vendored
91
dist/platforms/windows/activate.ps1
vendored
@@ -6,11 +6,88 @@ Write-Output "# Activating #"
|
|||||||
Write-Output "###########################"
|
Write-Output "###########################"
|
||||||
Write-Output ""
|
Write-Output ""
|
||||||
|
|
||||||
& "$Env:UNITY_PATH/Editor/Unity.exe" -batchmode -quit -nographics `
|
if ( ($null -ne ${env:UNITY_SERIAL}) -and ($null -ne ${env:UNITY_EMAIL}) -and ($null -ne ${env:UNITY_PASSWORD}) )
|
||||||
-username $Env:UNITY_EMAIL `
|
{
|
||||||
-password $Env:UNITY_PASSWORD `
|
#
|
||||||
-serial $Env:UNITY_SERIAL `
|
# SERIAL LICENSE MODE
|
||||||
-projectPath "c:/BlankProject" `
|
#
|
||||||
-logfile - | Out-Host
|
# This will activate unity, using the serial activation process.
|
||||||
|
#
|
||||||
|
Write-Output "Requesting activation"
|
||||||
|
|
||||||
$ACTIVATION_EXIT_CODE = $LASTEXITCODE
|
$ACTIVATION_OUTPUT = Start-Process -FilePath "$Env:UNITY_PATH/Editor/Unity.exe" `
|
||||||
|
-NoNewWindow `
|
||||||
|
-PassThru `
|
||||||
|
-ArgumentList "-batchmode `
|
||||||
|
-quit `
|
||||||
|
-nographics `
|
||||||
|
-username $Env:UNITY_EMAIL `
|
||||||
|
-password $Env:UNITY_PASSWORD `
|
||||||
|
-serial $Env:UNITY_SERIAL `
|
||||||
|
-projectPath c:/BlankProject `
|
||||||
|
-logfile -"
|
||||||
|
|
||||||
|
# Cache the handle so exit code works properly
|
||||||
|
# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait
|
||||||
|
$unityHandle = $ACTIVATION_OUTPUT.Handle
|
||||||
|
|
||||||
|
while ($true) {
|
||||||
|
if ($ACTIVATION_OUTPUT.HasExited) {
|
||||||
|
$ACTIVATION_EXIT_CODE = $ACTIVATION_OUTPUT.ExitCode
|
||||||
|
|
||||||
|
# Display results
|
||||||
|
if ($ACTIVATION_EXIT_CODE -eq 0)
|
||||||
|
{
|
||||||
|
Write-Output "Activation Succeeded"
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
Write-Output "Activation failed, with exit code $ACTIVATION_EXIT_CODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif( ($null -ne ${env:UNITY_LICENSING_SERVER}))
|
||||||
|
{
|
||||||
|
#
|
||||||
|
# Custom Unity License Server
|
||||||
|
#
|
||||||
|
|
||||||
|
Write-Output "Adding licensing server config"
|
||||||
|
|
||||||
|
$ACTIVATION_OUTPUT = Start-Process -FilePath "$Env:UNITY_PATH\Editor\Data\Resources\Licensing\Client\Unity.Licensing.Client.exe" `
|
||||||
|
-ArgumentList "--acquire-floating" `
|
||||||
|
-NoNewWindow `
|
||||||
|
-PassThru `
|
||||||
|
-Wait `
|
||||||
|
-RedirectStandardOutput "license.txt"
|
||||||
|
|
||||||
|
$PARSEDFILE = (Get-Content "license.txt" | Select-String -AllMatches -Pattern '\".*?\"' | ForEach-Object { $_.Matches.Value }) -replace '"'
|
||||||
|
|
||||||
|
$env:FLOATING_LICENSE = $PARSEDFILE[1]
|
||||||
|
$FLOATING_LICENSE_TIMEOUT = $PARSEDFILE[3]
|
||||||
|
|
||||||
|
Write-Output "Acquired floating license: ""$env:FLOATING_LICENSE"" with timeout $FLOATING_LICENSE_TIMEOUT"
|
||||||
|
# Store the exit code from the verify command
|
||||||
|
$ACTIVATION_EXIT_CODE = $ACTIVATION_OUTPUT.ExitCode
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#
|
||||||
|
# NO LICENSE ACTIVATION STRATEGY MATCHED
|
||||||
|
#
|
||||||
|
# This will exit since no activation strategies could be matched.
|
||||||
|
#
|
||||||
|
Write-Output "License activation strategy could not be determined."
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Visit https://game.ci/docs/github/activation for more"
|
||||||
|
Write-Output "details on how to set up one of the possible activation strategies."
|
||||||
|
|
||||||
|
Write-Output "::error ::No valid license activation strategy could be determined. Make sure to provide UNITY_EMAIL, UNITY_PASSWORD, and either a UNITY_SERIAL \
|
||||||
|
or UNITY_LICENSE. See more info at https://game.ci/docs/github/activation"
|
||||||
|
|
||||||
|
$ACTIVATION_EXIT_CODE = 1;
|
||||||
|
}
|
||||||
|
|||||||
70
dist/platforms/windows/build.ps1
vendored
70
dist/platforms/windows/build.ps1
vendored
@@ -16,6 +16,25 @@ Write-Output "$('Using build name "')$($Env:BUILD_NAME)$('".')"
|
|||||||
|
|
||||||
Write-Output "$('Using build target "')$($Env:BUILD_TARGET)$('".')"
|
Write-Output "$('Using build target "')$($Env:BUILD_TARGET)$('".')"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Display the build profile
|
||||||
|
#
|
||||||
|
|
||||||
|
if ($Env:BUILD_PROFILE)
|
||||||
|
{
|
||||||
|
# User has provided a path to a build profile `.asset` file
|
||||||
|
#
|
||||||
|
Write-Output "$('Using build profile "')$($Env:BUILD_PROFILE)$('" relative to "')$($Env:UNITY_PROJECT_PATH)$('".')"
|
||||||
|
#
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
# User has not provided a build profile
|
||||||
|
#
|
||||||
|
Write-Output "$('Doing a default "')$($Env:BUILD_TARGET)$('" platform build.')"
|
||||||
|
#
|
||||||
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Display build path and file
|
# Display build path and file
|
||||||
#
|
#
|
||||||
@@ -129,37 +148,52 @@ Write-Output "# Building project #"
|
|||||||
Write-Output "###########################"
|
Write-Output "###########################"
|
||||||
Write-Output ""
|
Write-Output ""
|
||||||
|
|
||||||
|
$unityGraphics = "-nographics"
|
||||||
|
|
||||||
|
if ($LLVMPIPE_INSTALLED -eq "true")
|
||||||
|
{
|
||||||
|
$unityGraphics = "-force-opengl"
|
||||||
|
}
|
||||||
|
|
||||||
# If $Env:CUSTOM_PARAMETERS contains spaces and is passed directly on the command line to Unity, powershell will wrap it
|
# If $Env:CUSTOM_PARAMETERS contains spaces and is passed directly on the command line to Unity, powershell will wrap it
|
||||||
# in double quotes. To avoid this, parse $Env:CUSTOM_PARAMETERS into an array, while respecting any quotations within the string.
|
# in double quotes. To avoid this, parse $Env:CUSTOM_PARAMETERS into an array, while respecting any quotations within the string.
|
||||||
$_, $customParametersArray = Invoke-Expression('Write-Output -- "" ' + $Env:CUSTOM_PARAMETERS)
|
$_, $customParametersArray = Invoke-Expression('Write-Output -- "" ' + $Env:CUSTOM_PARAMETERS)
|
||||||
$unityArgs = @(
|
$unityArgs = @(
|
||||||
"-quit",
|
"-quit",
|
||||||
"-batchmode",
|
"-batchmode",
|
||||||
"-nographics",
|
$unityGraphics,
|
||||||
"-silent-crashes",
|
"-silent-crashes",
|
||||||
"-projectPath", $Env:UNITY_PROJECT_PATH,
|
"-customBuildName", "`"$Env:BUILD_NAME`"",
|
||||||
"-executeMethod", $Env:BUILD_METHOD,
|
"-projectPath", "`"$Env:UNITY_PROJECT_PATH`"",
|
||||||
"-buildTarget", $Env:BUILD_TARGET,
|
"-executeMethod", "`"$Env:BUILD_METHOD`"",
|
||||||
"-customBuildTarget", $Env:BUILD_TARGET,
|
"-customBuildTarget", "`"$Env:BUILD_TARGET`"",
|
||||||
"-customBuildPath", $Env:CUSTOM_BUILD_PATH,
|
"-customBuildPath", "`"$Env:CUSTOM_BUILD_PATH`"",
|
||||||
"-buildVersion", $Env:VERSION,
|
"-customBuildProfile", "`"$Env:BUILD_PROFILE`"",
|
||||||
"-androidVersionCode", $Env:ANDROID_VERSION_CODE,
|
"-buildVersion", "`"$Env:VERSION`"",
|
||||||
"-androidKeystorePass", $Env:ANDROID_KEYSTORE_PASS,
|
"-androidVersionCode", "`"$Env:ANDROID_VERSION_CODE`"",
|
||||||
"-androidKeyaliasName", $Env:ANDROID_KEYALIAS_NAME,
|
"-androidKeystorePass", "`"$Env:ANDROID_KEYSTORE_PASS`"",
|
||||||
"-androidKeyaliasPass", $Env:ANDROID_KEYALIAS_PASS,
|
"-androidKeyaliasName", "`"$Env:ANDROID_KEYALIAS_NAME`"",
|
||||||
"-androidTargetSdkVersion", $Env:ANDROID_TARGET_SDK_VERSION,
|
"-androidKeyaliasPass", "`"$Env:ANDROID_KEYALIAS_PASS`"",
|
||||||
"-androidExportType", $Env:ANDROID_EXPORT_TYPE,
|
"-androidTargetSdkVersion", "`"$Env:ANDROID_TARGET_SDK_VERSION`"",
|
||||||
"-androidSymbolType", $Env:ANDROID_SYMBOL_TYPE,
|
"-androidExportType", "`"$Env:ANDROID_EXPORT_TYPE`"",
|
||||||
|
"-androidSymbolType", "`"$Env:ANDROID_SYMBOL_TYPE`"",
|
||||||
"-logfile", "-"
|
"-logfile", "-"
|
||||||
) + $customParametersArray
|
) + $customParametersArray
|
||||||
|
|
||||||
|
if (-not $Env:BUILD_PROFILE) {
|
||||||
|
$unityArgs += @("-buildTarget", "`"$Env:BUILD_TARGET`"")
|
||||||
|
}
|
||||||
|
if ($Env:BUILD_PROFILE) {
|
||||||
|
$unityArgs += @("-activeBuildProfile", "`"$Env:BUILD_PROFILE`"")
|
||||||
|
}
|
||||||
|
|
||||||
# Remove null items as that will fail the Start-Process call
|
# Remove null items as that will fail the Start-Process call
|
||||||
$unityArgs = $unityArgs | Where-Object { $_ -ne $null }
|
$unityArgs = $unityArgs | Where-Object { $_ -ne $null }
|
||||||
|
|
||||||
$unityProcess = Start-Process -FilePath "$Env:UNITY_PATH\Editor\Unity.exe" `
|
$unityProcess = Start-Process -FilePath "$Env:UNITY_PATH/Editor/Unity.exe" `
|
||||||
-ArgumentList $unityArgs `
|
-ArgumentList $unityArgs `
|
||||||
-PassThru `
|
-PassThru `
|
||||||
-NoNewWindow
|
-NoNewWindow
|
||||||
|
|
||||||
# Cache the handle so exit code works properly
|
# Cache the handle so exit code works properly
|
||||||
# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait
|
# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait
|
||||||
|
|||||||
35
dist/platforms/windows/entrypoint.ps1
vendored
35
dist/platforms/windows/entrypoint.ps1
vendored
@@ -1,8 +1,16 @@
|
|||||||
Get-Process
|
Get-Process
|
||||||
|
|
||||||
|
# Copy .upmconfig.toml if it exists
|
||||||
|
if (Test-Path "C:\githubhome\.upmconfig.toml") {
|
||||||
|
Write-Host "Copying .upmconfig.toml to $Env:USERPROFILE\.upmconfig.toml"
|
||||||
|
Copy-Item -Path "C:\githubhome\.upmconfig.toml" -Destination "$Env:USERPROFILE\.upmconfig.toml" -Force
|
||||||
|
} else {
|
||||||
|
Write-Host "No .upmconfig.toml found at C:\githubhome"
|
||||||
|
}
|
||||||
|
|
||||||
# Import any necessary registry keys, ie: location of windows 10 sdk
|
# Import any necessary registry keys, ie: location of windows 10 sdk
|
||||||
# No guarantee that there will be any necessary registry keys, ie: tvOS
|
# No guarantee that there will be any necessary registry keys, ie: tvOS
|
||||||
Get-ChildItem -Path c:\regkeys -File | ForEach-Object {reg import $_.fullname}
|
Get-ChildItem -Path c:\regkeys -File | ForEach-Object { reg import $_.fullname }
|
||||||
|
|
||||||
# Register the Visual Studio installation so Unity can find it
|
# Register the Visual Studio installation so Unity can find it
|
||||||
regsvr32 C:\ProgramData\Microsoft\VisualStudio\Setup\x64\Microsoft.VisualStudio.Setup.Configuration.Native.dll
|
regsvr32 C:\ProgramData\Microsoft\VisualStudio\Setup\x64\Microsoft.VisualStudio.Setup.Configuration.Native.dll
|
||||||
@@ -10,22 +18,37 @@ regsvr32 C:\ProgramData\Microsoft\VisualStudio\Setup\x64\Microsoft.VisualStudio.
|
|||||||
# Kill the regsvr process
|
# Kill the regsvr process
|
||||||
Get-Process -Name regsvr32 | ForEach-Object { Stop-Process -Id $_.Id -Force }
|
Get-Process -Name regsvr32 | ForEach-Object { Stop-Process -Id $_.Id -Force }
|
||||||
|
|
||||||
|
# Install Visual C++ 2013 Redistributables
|
||||||
|
. "c:\steps\install_vcredist13.ps1"
|
||||||
|
|
||||||
# Setup Git Credentials
|
# Setup Git Credentials
|
||||||
. "c:\steps\set_gitcredential.ps1"
|
. "c:\steps\set_gitcredential.ps1"
|
||||||
|
|
||||||
# Activate Unity
|
if ($env:ENABLE_GPU -eq "true") {
|
||||||
. "c:\steps\activate.ps1"
|
# Install LLVMpipe software graphics driver
|
||||||
|
. "c:\steps\install_llvmpipe.ps1"
|
||||||
|
}
|
||||||
|
|
||||||
# If we didn't activate successfully, exit with the exit code from the activation step.
|
# Activate Unity
|
||||||
if ($ACTIVATION_EXIT_CODE -ne 0) {
|
if ($env:SKIP_ACTIVATION -ne "true") {
|
||||||
|
. "c:\steps\activate.ps1"
|
||||||
|
|
||||||
|
# If we didn't activate successfully, exit with the exit code from the activation step.
|
||||||
|
if ($ACTIVATION_EXIT_CODE -ne 0) {
|
||||||
exit $ACTIVATION_EXIT_CODE
|
exit $ACTIVATION_EXIT_CODE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host "Skipping activation"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build the project
|
# Build the project
|
||||||
. "c:\steps\build.ps1"
|
. "c:\steps\build.ps1"
|
||||||
|
|
||||||
# Free the seat for the activated license
|
# Free the seat for the activated license
|
||||||
. "c:\steps\return_license.ps1"
|
if ($env:SKIP_ACTIVATION -ne "true") {
|
||||||
|
. "c:\steps\return_license.ps1"
|
||||||
|
}
|
||||||
|
|
||||||
Get-Process
|
Get-Process
|
||||||
|
|
||||||
|
|||||||
56
dist/platforms/windows/install_llvmpipe.ps1
vendored
Normal file
56
dist/platforms/windows/install_llvmpipe.ps1
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
$Private:repo = "mmozeiko/build-mesa"
|
||||||
|
$Private:downloadPath = "$Env:TEMP\mesa.zip"
|
||||||
|
$Private:extractPath = "$Env:TEMP\mesa"
|
||||||
|
$Private:destinationPath = "$Env:UNITY_PATH\Editor\"
|
||||||
|
$Private:version = "25.1.0"
|
||||||
|
|
||||||
|
$LLVMPIPE_INSTALLED = "false"
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Get the release info from GitHub API (version fixed to decrease probability of breakage)
|
||||||
|
$releaseUrl = "https://api.github.com/repos/$repo/releases/tags/$version"
|
||||||
|
$release = Invoke-RestMethod -Uri $releaseUrl -Headers @{ "User-Agent" = "PowerShell" }
|
||||||
|
|
||||||
|
# Get the download URL for the zip asset
|
||||||
|
$zipUrl = $release.assets | Where-Object { $_.name -like "mesa-llvmpipe-x64*.zip" } | Select-Object -First 1 -ExpandProperty browser_download_url
|
||||||
|
|
||||||
|
if (-not $zipUrl) {
|
||||||
|
throw "No zip file found in the latest release."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download the zip file
|
||||||
|
Write-Host "Downloading $zipUrl..."
|
||||||
|
Invoke-WebRequest -Uri $zipUrl -OutFile $downloadPath
|
||||||
|
|
||||||
|
# Create extraction directory if it doesn't exist
|
||||||
|
if (-not (Test-Path $extractPath)) {
|
||||||
|
New-Item -ItemType Directory -Path $extractPath | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the zip file
|
||||||
|
Write-Host "Extracting $downloadPath to $extractPath..."
|
||||||
|
Expand-Archive -Path $downloadPath -DestinationPath $extractPath -Force
|
||||||
|
|
||||||
|
# Create destination directory if it doesn't exist
|
||||||
|
if (-not (Test-Path $destinationPath)) {
|
||||||
|
New-Item -ItemType Directory -Path $destinationPath | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy extracted files to destination
|
||||||
|
Write-Host "Copying files to $destinationPath..."
|
||||||
|
Copy-Item -Path "$extractPath\*" -Destination $destinationPath -Recurse -Force
|
||||||
|
|
||||||
|
Write-Host "Successfully downloaded, extracted, and copied Mesa files to $destinationPath"
|
||||||
|
|
||||||
|
$LLVMPIPE_INSTALLED = "true"
|
||||||
|
} catch {
|
||||||
|
Write-Error "An error occurred: $_"
|
||||||
|
} finally {
|
||||||
|
# Clean up temporary files
|
||||||
|
if (Test-Path $downloadPath) {
|
||||||
|
Remove-Item $downloadPath -Force
|
||||||
|
}
|
||||||
|
if (Test-Path $extractPath) {
|
||||||
|
Remove-Item $extractPath -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
11
dist/platforms/windows/install_vcredist13.ps1
vendored
Normal file
11
dist/platforms/windows/install_vcredist13.ps1
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# For some reason, Unity is failing in github actions windows runners
|
||||||
|
# due to missing Visual C++ 2013 redistributables.
|
||||||
|
# This script downloads and installs the required redistributables.
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "#########################################################"
|
||||||
|
Write-Output "# Installing Visual C++ Redistributables (2013) #"
|
||||||
|
Write-Output "#########################################################"
|
||||||
|
Write-Output ""
|
||||||
|
|
||||||
|
|
||||||
|
choco install vcredist2013 -y --no-progress
|
||||||
59
dist/platforms/windows/return_license.ps1
vendored
59
dist/platforms/windows/return_license.ps1
vendored
@@ -6,9 +6,56 @@ Write-Output "# Return License #"
|
|||||||
Write-Output "###########################"
|
Write-Output "###########################"
|
||||||
Write-Output ""
|
Write-Output ""
|
||||||
|
|
||||||
& "$Env:UNITY_PATH\Editor\Unity.exe" -batchmode -quit -nographics `
|
if (($null -ne ${env:UNITY_LICENSING_SERVER}))
|
||||||
-username $Env:UNITY_EMAIL `
|
{
|
||||||
-password $Env:UNITY_PASSWORD `
|
Write-Output "Returning floating license: ""$env:FLOATING_LICENSE"""
|
||||||
-returnlicense `
|
Start-Process -FilePath "$Env:UNITY_PATH\Editor\Data\Resources\Licensing\Client\Unity.Licensing.Client.exe" `
|
||||||
-projectPath "c:/BlankProject" `
|
-ArgumentList "--return-floating ""$env:FLOATING_LICENSE"" " `
|
||||||
-logfile - | Out-Host
|
-NoNewWindow `
|
||||||
|
-Wait
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif (($null -ne ${env:UNITY_SERIAL}) -and ($null -ne ${env:UNITY_EMAIL}) -and ($null -ne ${env:UNITY_PASSWORD}))
|
||||||
|
{
|
||||||
|
#
|
||||||
|
# SERIAL LICENSE MODE
|
||||||
|
#
|
||||||
|
# This will return the license that is currently in use.
|
||||||
|
#
|
||||||
|
$RETURN_LICENSE_OUTPUT = Start-Process -FilePath "$Env:UNITY_PATH/Editor/Unity.exe" `
|
||||||
|
-NoNewWindow `
|
||||||
|
-PassThru `
|
||||||
|
-ArgumentList "-batchmode `
|
||||||
|
-quit `
|
||||||
|
-nographics `
|
||||||
|
-username $Env:UNITY_EMAIL `
|
||||||
|
-password $Env:UNITY_PASSWORD `
|
||||||
|
-returnlicense `
|
||||||
|
-projectPath c:/BlankProject `
|
||||||
|
-logfile -"
|
||||||
|
|
||||||
|
# Cache the handle so exit code works properly
|
||||||
|
# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait
|
||||||
|
$unityHandle = $RETURN_LICENSE_OUTPUT.Handle
|
||||||
|
|
||||||
|
while ($true) {
|
||||||
|
if ($RETURN_LICENSE_OUTPUT.HasExited) {
|
||||||
|
$RETURN_LICENSE_EXIT_CODE = $RETURN_LICENSE_OUTPUT.ExitCode
|
||||||
|
|
||||||
|
# Display results
|
||||||
|
if ($RETURN_LICENSE_EXIT_CODE -eq 0)
|
||||||
|
{
|
||||||
|
Write-Output "License Return Succeeded"
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
Write-Output "License Return failed, with exit code $RETURN_LICENSE_EXIT_CODE"
|
||||||
|
Write-Output "::warning ::License Return failed! If this is a Pro License you might need to manually `
|
||||||
|
free the seat in your Unity admin panel or you might run out of seats to activate with."
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
dist/platforms/windows/set_gitcredential.ps1
vendored
14
dist/platforms/windows/set_gitcredential.ps1
vendored
@@ -1,16 +1,16 @@
|
|||||||
if ([string]::IsNullOrEmpty($env:GIT_PRIVATE_TOKEN)) {
|
if ($null -eq ${env:GIT_PRIVATE_TOKEN}) {
|
||||||
Write-Host "GIT_PRIVATE_TOKEN unset skipping"
|
Write-Host "GIT_PRIVATE_TOKEN unset skipping"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Host "GIT_PRIVATE_TOKEN is set configuring git credentials"
|
Write-Host "GIT_PRIVATE_TOKEN is set configuring git credentials"
|
||||||
|
|
||||||
git config --global credential.helper store
|
git config --global credential.helper store
|
||||||
git config --global --replace-all "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
|
git config --global --replace-all url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
|
||||||
git config --global --add "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com"
|
git config --global --add url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com"
|
||||||
git config --global --add "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "https://github.com/"
|
git config --global --add url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "https://github.com/"
|
||||||
|
|
||||||
git config --global "url.https://ssh:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
|
git config --global url."https://ssh:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
|
||||||
git config --global "url.https://git:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com:"
|
git config --global url."https://git:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com:"
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "---------- git config --list -------------"
|
Write-Host "---------- git config --list -------------"
|
||||||
|
|||||||
11
jest.ci.config.js
Normal file
11
jest.ci.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const base = require('./jest.config.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...base,
|
||||||
|
forceExit: true,
|
||||||
|
detectOpenHandles: true,
|
||||||
|
testTimeout: 120000,
|
||||||
|
maxWorkers: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -25,6 +25,6 @@ module.exports = {
|
|||||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
modulePathIgnorePatterns: ['<rootDir>/lib/', '<rootDir>/dist/'],
|
modulePathIgnorePatterns: ['<rootDir>/lib/', '<rootDir>/dist/'],
|
||||||
|
|
||||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
// Use jest.setup.js to polyfill fetch for all tests
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/jest.setup.ts'],
|
setupFiles: ['<rootDir>/jest.setup.js'],
|
||||||
};
|
};
|
||||||
|
|||||||
2
jest.setup.js
Normal file
2
jest.setup.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const fetch = require('node-fetch');
|
||||||
|
global.fetch = fetch;
|
||||||
42
package.json
42
package.json
@@ -7,48 +7,56 @@
|
|||||||
"author": "Webber <webber@takken.io>",
|
"author": "Webber <webber@takken.io>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "lefthook install && npx husky uninstall -y",
|
"prepare": "lefthook install",
|
||||||
"build": "yarn && tsc && ncc build lib --source-map --license licenses.txt",
|
"build": "yarn && tsc && ncc build lib --source-map --license licenses.txt",
|
||||||
"lint": "prettier --check \"src/**/*.{js,ts}\" && eslint src/**/*.ts",
|
"lint": "prettier --check \"src/**/*.{js,ts}\" && eslint src/**/*.ts",
|
||||||
"format": "prettier --write \"src/**/*.{js,ts}\"",
|
"format": "prettier --write \"src/**/*.{js,ts}\"",
|
||||||
"cli": "yarn ts-node src/index.ts -m cli",
|
"cli": "yarn ts-node src/index.ts -m cli",
|
||||||
"gcp-secrets-tests": "cross-env providerStrategy=aws cloudRunnerTests=true readInputOverrideCommand=\"gcp-secret-manager\" populateOverride=true readInputFromOverrideList=UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD yarn test -i -t \"cloud runner\"",
|
"gcp-secrets-tests": "cross-env providerStrategy=aws orchestratorTests=true inputPullCommand=\"gcp-secret-manager\" populateOverride=true pullInputList=UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD yarn test -i -t \"orchestrator\"",
|
||||||
"gcp-secrets-cli": "cross-env cloudRunnerTests=true readInputOverrideCommand=\"gcp-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --readInputFromOverrideList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
|
"gcp-secrets-cli": "cross-env orchestratorTests=true USE_IL2CPP=false inputPullCommand=\"gcp-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --pullInputList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
|
||||||
"aws-secrets-cli": "cross-env cloudRunnerTests=true readInputOverrideCommand=\"aws-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --readInputFromOverrideList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
|
"aws-secrets-cli": "cross-env orchestratorTests=true inputPullCommand=\"aws-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --pullInputList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
|
||||||
"cli-aws": "cross-env providerStrategy=aws yarn run test-cli",
|
"cli-aws": "cross-env providerStrategy=aws yarn run test-cli",
|
||||||
"cli-k8s": "cross-env providerStrategy=k8s yarn run test-cli",
|
"cli-k8s": "cross-env providerStrategy=k8s yarn run test-cli",
|
||||||
"test-cli": "cross-env cloudRunnerTests=true yarn ts-node src/index.ts -m cli --projectPath test-project",
|
"test-cli": "cross-env orchestratorTests=true yarn ts-node src/index.ts -m cli --projectPath test-project",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test-i": "cross-env cloudRunnerTests=true yarn test -i -t \"cloud runner\"",
|
"test:ci": "jest --config=jest.ci.config.js --runInBand",
|
||||||
|
"test-i": "cross-env orchestratorTests=true yarn test -i -t \"orchestrator\"",
|
||||||
"test-i-*": "yarn run test-i-aws && yarn run test-i-k8s",
|
"test-i-*": "yarn run test-i-aws && yarn run test-i-k8s",
|
||||||
"test-i-aws": "cross-env cloudRunnerTests=true providerStrategy=aws yarn test -i -t \"cloud runner\"",
|
"test-i-aws": "cross-env orchestratorTests=true providerStrategy=aws yarn test -i -t \"orchestrator\"",
|
||||||
"test-i-k8s": "cross-env cloudRunnerTests=true providerStrategy=k8s yarn test -i -t \"cloud runner\""
|
"test-i-k8s": "cross-env orchestratorTests=true providerStrategy=k8s yarn test -i -t \"orchestrator\""
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.x"
|
"node": ">=18.x"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/cache": "^3.1.3",
|
"@actions/cache": "^4.0.0",
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^1.1.0",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/github": "^5.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
|
"@aws-sdk/client-cloudformation": "^3.777.0",
|
||||||
|
"@aws-sdk/client-cloudwatch-logs": "^3.777.0",
|
||||||
|
"@aws-sdk/client-ecs": "^3.778.0",
|
||||||
|
"@aws-sdk/client-kinesis": "^3.777.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.779.0",
|
||||||
"@kubernetes/client-node": "^0.16.3",
|
"@kubernetes/client-node": "^0.16.3",
|
||||||
"@octokit/core": "^3.5.1",
|
"@octokit/core": "^5.1.0",
|
||||||
"async-wait-until": "^2.0.12",
|
"async-wait-until": "^2.0.12",
|
||||||
"aws-sdk": "^2.1081.0",
|
"aws-sdk": "^2.1081.0",
|
||||||
"base-64": "^1.0.0",
|
"base-64": "^1.0.0",
|
||||||
"commander": "^9.0.0",
|
"commander": "^9.0.0",
|
||||||
"commander-ts": "^0.2.0",
|
"commander-ts": "^0.2.0",
|
||||||
"kubernetes-client": "^9.0.0",
|
"kubernetes-client": "^9.0.0",
|
||||||
|
"md5": "^2.3.0",
|
||||||
"nanoid": "^3.3.1",
|
"nanoid": "^3.3.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"semver": "^7.5.2",
|
"semver": "^7.5.2",
|
||||||
"unity-changeset": "^2.0.0",
|
"shell-quote": "^1.8.3",
|
||||||
|
"ts-md5": "^1.3.1",
|
||||||
|
"unity-changeset": "^3.1.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@evilmartians/lefthook": "^1.2.9",
|
|
||||||
"@types/base-64": "^1.0.0",
|
"@types/base-64": "^1.0.0",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/node": "^17.0.23",
|
"@types/node": "^17.0.23",
|
||||||
@@ -67,9 +75,11 @@
|
|||||||
"jest-circus": "^27.5.1",
|
"jest-circus": "^27.5.1",
|
||||||
"jest-fail-on-console": "^3.0.2",
|
"jest-fail-on-console": "^3.0.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
"lefthook": "^1.6.1",
|
||||||
|
"node-fetch": "2",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^27.1.3",
|
||||||
"ts-node": "10.4.0",
|
"ts-node": "10.8.1",
|
||||||
"typescript": "4.7.4",
|
"typescript": "4.7.4",
|
||||||
"yarn-audit-fix": "^9.3.8"
|
"yarn-audit-fix": "^9.3.8"
|
||||||
},
|
},
|
||||||
|
|||||||
15
scripts/game-ci.bat
Normal file
15
scripts/game-ci.bat
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
echo "installing game-ci cli"
|
||||||
|
if exist %UserProfile%\AppData\LocalLow\game-ci\ (
|
||||||
|
echo Installed Updating
|
||||||
|
git -C %UserProfile%\AppData\LocalLow\game-ci\ fetch
|
||||||
|
git -C %UserProfile%\AppData\LocalLow\game-ci\ reset --hard
|
||||||
|
git -C %UserProfile%\AppData\LocalLow\game-ci\ pull
|
||||||
|
git -C %UserProfile%\AppData\LocalLow\game-ci\ branch
|
||||||
|
) else (
|
||||||
|
echo Not Installed Downloading...
|
||||||
|
mkdir %UserProfile%\AppData\LocalLow\game-ci\
|
||||||
|
git clone https://github.com/game-ci/unity-builder %UserProfile%\AppData\LocalLow\game-ci\
|
||||||
|
)
|
||||||
|
|
||||||
|
call yarn --cwd %UserProfile%\AppData\LocalLow\game-ci\ install
|
||||||
|
call yarn --cwd %UserProfile%\AppData\LocalLow\game-ci\ run gcp-secrets-cli %* --projectPath %cd% --awsStackName game-ci-cli
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import { Action, BuildParameters, Cache, CloudRunner, Docker, ImageTag, Output } from './model';
|
import { Action, BuildParameters, Cache, Orchestrator, Docker, ImageTag, Output } from './model';
|
||||||
import { Cli } from './model/cli/cli';
|
import { Cli } from './model/cli/cli';
|
||||||
import MacBuilder from './model/mac-builder';
|
import MacBuilder from './model/mac-builder';
|
||||||
import PlatformSetup from './model/platform-setup';
|
import PlatformSetup from './model/platform-setup';
|
||||||
@@ -33,7 +33,8 @@ async function runMain() {
|
|||||||
...buildParameters,
|
...buildParameters,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await CloudRunner.run(buildParameters, baseImage.toString());
|
await Orchestrator.run(buildParameters, baseImage.toString());
|
||||||
|
exitCode = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set output
|
// Set output
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Integration test for exercising real GitHub check creation and updates.
|
||||||
|
import Orchestrator from '../model/orchestrator/orchestrator';
|
||||||
|
import UnityVersioning from '../model/unity-versioning';
|
||||||
|
import GitHub from '../model/github';
|
||||||
|
import { TIMEOUT_INFINITE, createParameters } from '../test-utils/orchestrator-test-helpers';
|
||||||
|
|
||||||
|
const runIntegration = process.env.RUN_GITHUB_INTEGRATION_TESTS === 'true';
|
||||||
|
const describeOrSkip = runIntegration ? describe : describe.skip;
|
||||||
|
|
||||||
|
describeOrSkip('Orchestrator Github Checks Integration', () => {
|
||||||
|
it(
|
||||||
|
'creates and updates a real GitHub check',
|
||||||
|
async () => {
|
||||||
|
const buildParameter = await createParameters({
|
||||||
|
versioning: 'None',
|
||||||
|
projectPath: 'test-project',
|
||||||
|
unityVersion: UnityVersioning.read('test-project'),
|
||||||
|
asyncOrchestrator: `true`,
|
||||||
|
githubChecks: `true`,
|
||||||
|
});
|
||||||
|
await Orchestrator.setup(buildParameter);
|
||||||
|
const checkId = await GitHub.createGitHubCheck(`integration create`);
|
||||||
|
expect(checkId).not.toEqual('');
|
||||||
|
await GitHub.updateGitHubCheck(`1 ${new Date().toISOString()}`, `integration`);
|
||||||
|
await GitHub.updateGitHubCheck(`2 ${new Date().toISOString()}`, `integration`, `success`, `completed`);
|
||||||
|
},
|
||||||
|
TIMEOUT_INFINITE,
|
||||||
|
);
|
||||||
|
});
|
||||||
3
src/jest.globals.ts
Normal file
3
src/jest.globals.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { fetch as undiciFetch, Headers, Request, Response } from 'undici';
|
||||||
|
|
||||||
|
Object.assign(globalThis, { fetch: undiciFetch, Headers, Request, Response });
|
||||||
@@ -71,6 +71,12 @@ describe('BuildParameters', () => {
|
|||||||
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue }));
|
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns the build profile', async () => {
|
||||||
|
const mockValue = 'path/to/build_profile.asset';
|
||||||
|
jest.spyOn(Input, 'buildProfile', 'get').mockReturnValue(mockValue);
|
||||||
|
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildProfile: mockValue }));
|
||||||
|
});
|
||||||
|
|
||||||
it('returns the build name', async () => {
|
it('returns the build name', async () => {
|
||||||
const mockValue = 'someBuildName';
|
const mockValue = 'someBuildName';
|
||||||
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
|
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
import AndroidVersioning from './android-versioning';
|
import AndroidVersioning from './android-versioning';
|
||||||
import CloudRunnerConstants from './cloud-runner/options/cloud-runner-constants';
|
import OrchestratorConstants from './orchestrator/options/orchestrator-constants';
|
||||||
import CloudRunnerBuildGuid from './cloud-runner/options/cloud-runner-guid';
|
import OrchestratorBuildGuid from './orchestrator/options/orchestrator-guid';
|
||||||
import Input from './input';
|
import Input from './input';
|
||||||
import Platform from './platform';
|
import Platform from './platform';
|
||||||
import UnityVersioning from './unity-versioning';
|
import UnityVersioning from './unity-versioning';
|
||||||
@@ -10,8 +10,8 @@ import { GitRepoReader } from './input-readers/git-repo';
|
|||||||
import { GithubCliReader } from './input-readers/github-cli';
|
import { GithubCliReader } from './input-readers/github-cli';
|
||||||
import { Cli } from './cli/cli';
|
import { Cli } from './cli/cli';
|
||||||
import GitHub from './github';
|
import GitHub from './github';
|
||||||
import CloudRunnerOptions from './cloud-runner/options/cloud-runner-options';
|
import OrchestratorOptions from './orchestrator/options/orchestrator-options';
|
||||||
import CloudRunner from './cloud-runner/cloud-runner';
|
import Orchestrator from './orchestrator/orchestrator';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
|
|
||||||
class BuildParameters {
|
class BuildParameters {
|
||||||
@@ -22,15 +22,18 @@ class BuildParameters {
|
|||||||
public customImage!: string;
|
public customImage!: string;
|
||||||
public unitySerial!: string;
|
public unitySerial!: string;
|
||||||
public unityLicensingServer!: string;
|
public unityLicensingServer!: string;
|
||||||
|
public skipActivation!: string;
|
||||||
public runnerTempPath!: string;
|
public runnerTempPath!: string;
|
||||||
public targetPlatform!: string;
|
public targetPlatform!: string;
|
||||||
public projectPath!: string;
|
public projectPath!: string;
|
||||||
|
public buildProfile!: string;
|
||||||
public buildName!: string;
|
public buildName!: string;
|
||||||
public buildPath!: string;
|
public buildPath!: string;
|
||||||
public buildFile!: string;
|
public buildFile!: string;
|
||||||
public buildMethod!: string;
|
public buildMethod!: string;
|
||||||
public buildVersion!: string;
|
public buildVersion!: string;
|
||||||
public manualExit!: boolean;
|
public manualExit!: boolean;
|
||||||
|
public enableGpu!: boolean;
|
||||||
public androidVersionCode!: string;
|
public androidVersionCode!: string;
|
||||||
public androidKeystoreName!: string;
|
public androidKeystoreName!: string;
|
||||||
public androidKeystoreBase64!: string;
|
public androidKeystoreBase64!: string;
|
||||||
@@ -51,15 +54,25 @@ class BuildParameters {
|
|||||||
public sshAgent!: string;
|
public sshAgent!: string;
|
||||||
public sshPublicKeysDirectoryPath!: string;
|
public sshPublicKeysDirectoryPath!: string;
|
||||||
public providerStrategy!: string;
|
public providerStrategy!: string;
|
||||||
|
public gitAuthMode!: string;
|
||||||
public gitPrivateToken!: string;
|
public gitPrivateToken!: string;
|
||||||
public awsStackName!: string;
|
public awsStackName!: string;
|
||||||
|
public awsEndpoint?: string;
|
||||||
|
public awsCloudFormationEndpoint?: string;
|
||||||
|
public awsEcsEndpoint?: string;
|
||||||
|
public awsKinesisEndpoint?: string;
|
||||||
|
public awsCloudWatchLogsEndpoint?: string;
|
||||||
|
public awsS3Endpoint?: string;
|
||||||
|
public storageProvider!: string;
|
||||||
|
public rcloneRemote!: string;
|
||||||
public kubeConfig!: string;
|
public kubeConfig!: string;
|
||||||
public containerMemory!: string;
|
public containerMemory!: string;
|
||||||
public containerCpu!: string;
|
public containerCpu!: string;
|
||||||
|
public containerNamespace!: string;
|
||||||
public kubeVolumeSize!: string;
|
public kubeVolumeSize!: string;
|
||||||
public kubeVolume!: string;
|
public kubeVolume!: string;
|
||||||
public kubeStorageClass!: string;
|
public kubeStorageClass!: string;
|
||||||
public runAsHostUser!: String;
|
public runAsHostUser!: string;
|
||||||
public chownFilesTo!: string;
|
public chownFilesTo!: string;
|
||||||
public commandHooks!: string;
|
public commandHooks!: string;
|
||||||
public pullInputList!: string[];
|
public pullInputList!: string[];
|
||||||
@@ -72,11 +85,13 @@ class BuildParameters {
|
|||||||
public runNumber!: string;
|
public runNumber!: string;
|
||||||
public branch!: string;
|
public branch!: string;
|
||||||
public githubRepo!: string;
|
public githubRepo!: string;
|
||||||
|
public orchestratorRepoName!: string;
|
||||||
|
public cloneDepth!: number;
|
||||||
public gitSha!: string;
|
public gitSha!: string;
|
||||||
public logId!: string;
|
public logId!: string;
|
||||||
public buildGuid!: string;
|
public buildGuid!: string;
|
||||||
public cloudRunnerBranch!: string;
|
public orchestratorBranch!: string;
|
||||||
public cloudRunnerDebug!: boolean | undefined;
|
public orchestratorDebug!: boolean | undefined;
|
||||||
public buildPlatform!: string | undefined;
|
public buildPlatform!: string | undefined;
|
||||||
public isCliMode!: boolean;
|
public isCliMode!: boolean;
|
||||||
public maxRetainedWorkspaces!: number;
|
public maxRetainedWorkspaces!: number;
|
||||||
@@ -94,7 +109,7 @@ class BuildParameters {
|
|||||||
public dockerWorkspacePath!: string;
|
public dockerWorkspacePath!: string;
|
||||||
|
|
||||||
public static shouldUseRetainedWorkspaceMode(buildParameters: BuildParameters) {
|
public static shouldUseRetainedWorkspaceMode(buildParameters: BuildParameters) {
|
||||||
return buildParameters.maxRetainedWorkspaces > 0 && CloudRunner.lockedWorkspace !== ``;
|
return buildParameters.maxRetainedWorkspaces > 0 && Orchestrator.lockedWorkspace !== ``;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async create(): Promise<BuildParameters> {
|
static async create(): Promise<BuildParameters> {
|
||||||
@@ -146,15 +161,18 @@ class BuildParameters {
|
|||||||
customImage: Input.customImage,
|
customImage: Input.customImage,
|
||||||
unitySerial,
|
unitySerial,
|
||||||
unityLicensingServer: Input.unityLicensingServer,
|
unityLicensingServer: Input.unityLicensingServer,
|
||||||
|
skipActivation: Input.skipActivation,
|
||||||
runnerTempPath: Input.runnerTempPath,
|
runnerTempPath: Input.runnerTempPath,
|
||||||
targetPlatform: Input.targetPlatform,
|
targetPlatform: Input.targetPlatform,
|
||||||
projectPath: Input.projectPath,
|
projectPath: Input.projectPath,
|
||||||
|
buildProfile: Input.buildProfile,
|
||||||
buildName: Input.buildName,
|
buildName: Input.buildName,
|
||||||
buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
|
buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
|
||||||
buildFile,
|
buildFile,
|
||||||
buildMethod: Input.buildMethod,
|
buildMethod: Input.buildMethod,
|
||||||
buildVersion,
|
buildVersion,
|
||||||
manualExit: Input.manualExit,
|
manualExit: Input.manualExit,
|
||||||
|
enableGpu: Input.enableGpu,
|
||||||
androidVersionCode,
|
androidVersionCode,
|
||||||
androidKeystoreName: Input.androidKeystoreName,
|
androidKeystoreName: Input.androidKeystoreName,
|
||||||
androidKeystoreBase64: Input.androidKeystoreBase64,
|
androidKeystoreBase64: Input.androidKeystoreBase64,
|
||||||
@@ -168,7 +186,7 @@ class BuildParameters {
|
|||||||
customParameters: Input.customParameters,
|
customParameters: Input.customParameters,
|
||||||
sshAgent: Input.sshAgent,
|
sshAgent: Input.sshAgent,
|
||||||
sshPublicKeysDirectoryPath: Input.sshPublicKeysDirectoryPath,
|
sshPublicKeysDirectoryPath: Input.sshPublicKeysDirectoryPath,
|
||||||
gitPrivateToken: Input.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()),
|
gitPrivateToken: Input.gitPrivateToken ?? (await GithubCliReader.GetGitHubAuthToken()),
|
||||||
runAsHostUser: Input.runAsHostUser,
|
runAsHostUser: Input.runAsHostUser,
|
||||||
chownFilesTo: Input.chownFilesTo,
|
chownFilesTo: Input.chownFilesTo,
|
||||||
dockerCpuLimit: Input.dockerCpuLimit,
|
dockerCpuLimit: Input.dockerCpuLimit,
|
||||||
@@ -176,41 +194,53 @@ class BuildParameters {
|
|||||||
dockerIsolationMode: Input.dockerIsolationMode,
|
dockerIsolationMode: Input.dockerIsolationMode,
|
||||||
containerRegistryRepository: Input.containerRegistryRepository,
|
containerRegistryRepository: Input.containerRegistryRepository,
|
||||||
containerRegistryImageVersion: Input.containerRegistryImageVersion,
|
containerRegistryImageVersion: Input.containerRegistryImageVersion,
|
||||||
providerStrategy: CloudRunnerOptions.providerStrategy,
|
providerStrategy: OrchestratorOptions.providerStrategy,
|
||||||
buildPlatform: CloudRunnerOptions.buildPlatform,
|
gitAuthMode: OrchestratorOptions.gitAuthMode,
|
||||||
kubeConfig: CloudRunnerOptions.kubeConfig,
|
buildPlatform: OrchestratorOptions.buildPlatform,
|
||||||
containerMemory: CloudRunnerOptions.containerMemory,
|
kubeConfig: OrchestratorOptions.kubeConfig,
|
||||||
containerCpu: CloudRunnerOptions.containerCpu,
|
containerMemory: OrchestratorOptions.containerMemory,
|
||||||
kubeVolumeSize: CloudRunnerOptions.kubeVolumeSize,
|
containerCpu: OrchestratorOptions.containerCpu,
|
||||||
kubeVolume: CloudRunnerOptions.kubeVolume,
|
containerNamespace: OrchestratorOptions.containerNamespace,
|
||||||
postBuildContainerHooks: CloudRunnerOptions.postBuildContainerHooks,
|
kubeVolumeSize: OrchestratorOptions.kubeVolumeSize,
|
||||||
preBuildContainerHooks: CloudRunnerOptions.preBuildContainerHooks,
|
kubeVolume: OrchestratorOptions.kubeVolume,
|
||||||
customJob: CloudRunnerOptions.customJob,
|
postBuildContainerHooks: OrchestratorOptions.postBuildContainerHooks,
|
||||||
|
preBuildContainerHooks: OrchestratorOptions.preBuildContainerHooks,
|
||||||
|
customJob: OrchestratorOptions.customJob,
|
||||||
runNumber: Input.runNumber,
|
runNumber: Input.runNumber,
|
||||||
branch: Input.branch.replace('/head', '') || (await GitRepoReader.GetBranch()),
|
branch: Input.branch.replace('/head', '') || (await GitRepoReader.GetBranch()),
|
||||||
cloudRunnerBranch: CloudRunnerOptions.cloudRunnerBranch.split('/').reverse()[0],
|
orchestratorBranch: OrchestratorOptions.orchestratorBranch.split('/').reverse()[0],
|
||||||
cloudRunnerDebug: CloudRunnerOptions.cloudRunnerDebug,
|
orchestratorDebug: OrchestratorOptions.orchestratorDebug,
|
||||||
githubRepo: Input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder',
|
githubRepo: (Input.githubRepo ?? (await GitRepoReader.GetRemote())) || OrchestratorOptions.orchestratorRepoName,
|
||||||
|
orchestratorRepoName: OrchestratorOptions.orchestratorRepoName,
|
||||||
|
cloneDepth: Number.parseInt(OrchestratorOptions.cloneDepth),
|
||||||
isCliMode: Cli.isCliMode,
|
isCliMode: Cli.isCliMode,
|
||||||
awsStackName: CloudRunnerOptions.awsStackName,
|
awsStackName: OrchestratorOptions.awsStackName,
|
||||||
|
awsEndpoint: OrchestratorOptions.awsEndpoint,
|
||||||
|
awsCloudFormationEndpoint: OrchestratorOptions.awsCloudFormationEndpoint,
|
||||||
|
awsEcsEndpoint: OrchestratorOptions.awsEcsEndpoint,
|
||||||
|
awsKinesisEndpoint: OrchestratorOptions.awsKinesisEndpoint,
|
||||||
|
awsCloudWatchLogsEndpoint: OrchestratorOptions.awsCloudWatchLogsEndpoint,
|
||||||
|
awsS3Endpoint: OrchestratorOptions.awsS3Endpoint,
|
||||||
|
storageProvider: OrchestratorOptions.storageProvider,
|
||||||
|
rcloneRemote: OrchestratorOptions.rcloneRemote,
|
||||||
gitSha: Input.gitSha,
|
gitSha: Input.gitSha,
|
||||||
logId: customAlphabet(CloudRunnerConstants.alphabet, 9)(),
|
logId: customAlphabet(OrchestratorConstants.alphabet, 9)(),
|
||||||
buildGuid: CloudRunnerBuildGuid.generateGuid(Input.runNumber, Input.targetPlatform),
|
buildGuid: OrchestratorBuildGuid.generateGuid(Input.runNumber, Input.targetPlatform),
|
||||||
commandHooks: CloudRunnerOptions.commandHooks,
|
commandHooks: OrchestratorOptions.commandHooks,
|
||||||
inputPullCommand: CloudRunnerOptions.inputPullCommand,
|
inputPullCommand: OrchestratorOptions.inputPullCommand,
|
||||||
pullInputList: CloudRunnerOptions.pullInputList,
|
pullInputList: OrchestratorOptions.pullInputList,
|
||||||
kubeStorageClass: CloudRunnerOptions.kubeStorageClass,
|
kubeStorageClass: OrchestratorOptions.kubeStorageClass,
|
||||||
cacheKey: CloudRunnerOptions.cacheKey,
|
cacheKey: OrchestratorOptions.cacheKey,
|
||||||
maxRetainedWorkspaces: Number.parseInt(CloudRunnerOptions.maxRetainedWorkspaces),
|
maxRetainedWorkspaces: Number.parseInt(OrchestratorOptions.maxRetainedWorkspaces),
|
||||||
useLargePackages: CloudRunnerOptions.useLargePackages,
|
useLargePackages: OrchestratorOptions.useLargePackages,
|
||||||
useCompressionStrategy: CloudRunnerOptions.useCompressionStrategy,
|
useCompressionStrategy: OrchestratorOptions.useCompressionStrategy,
|
||||||
garbageMaxAge: CloudRunnerOptions.garbageMaxAge,
|
garbageMaxAge: OrchestratorOptions.garbageMaxAge,
|
||||||
githubChecks: CloudRunnerOptions.githubChecks,
|
githubChecks: OrchestratorOptions.githubChecks,
|
||||||
asyncWorkflow: CloudRunnerOptions.asyncCloudRunner,
|
asyncWorkflow: OrchestratorOptions.asyncOrchestrator,
|
||||||
githubCheckId: CloudRunnerOptions.githubCheckId,
|
githubCheckId: OrchestratorOptions.githubCheckId,
|
||||||
finalHooks: CloudRunnerOptions.finalHooks,
|
finalHooks: OrchestratorOptions.finalHooks,
|
||||||
skipLfs: CloudRunnerOptions.skipLfs,
|
skipLfs: OrchestratorOptions.skipLfs,
|
||||||
skipCache: CloudRunnerOptions.skipCache,
|
skipCache: OrchestratorOptions.skipCache,
|
||||||
cacheUnityInstallationOnMac: Input.cacheUnityInstallationOnMac,
|
cacheUnityInstallationOnMac: Input.cacheUnityInstallationOnMac,
|
||||||
unityHubVersionOnMac: Input.unityHubVersionOnMac,
|
unityHubVersionOnMac: Input.unityHubVersionOnMac,
|
||||||
dockerWorkspacePath: Input.dockerWorkspacePath,
|
dockerWorkspacePath: Input.dockerWorkspacePath,
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { Command } from 'commander-ts';
|
import { Command } from 'commander-ts';
|
||||||
import { BuildParameters, CloudRunner, ImageTag, Input } from '..';
|
import { BuildParameters, Orchestrator, ImageTag, Input } from '..';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import { ActionYamlReader } from '../input-readers/action-yaml';
|
import { ActionYamlReader } from '../input-readers/action-yaml';
|
||||||
import CloudRunnerLogger from '../cloud-runner/services/core/cloud-runner-logger';
|
import OrchestratorLogger from '../orchestrator/services/core/orchestrator-logger';
|
||||||
import CloudRunnerQueryOverride from '../cloud-runner/options/cloud-runner-query-override';
|
import OrchestratorQueryOverride from '../orchestrator/options/orchestrator-query-override';
|
||||||
import { CliFunction, CliFunctionsRepository } from './cli-functions-repository';
|
import { CliFunction, CliFunctionsRepository } from './cli-functions-repository';
|
||||||
import { Caching } from '../cloud-runner/remote-client/caching';
|
import { Caching } from '../orchestrator/remote-client/caching';
|
||||||
import { LfsHashing } from '../cloud-runner/services/utility/lfs-hashing';
|
import { LfsHashing } from '../orchestrator/services/utility/lfs-hashing';
|
||||||
import { RemoteClient } from '../cloud-runner/remote-client';
|
import { RemoteClient } from '../orchestrator/remote-client';
|
||||||
import CloudRunnerOptionsReader from '../cloud-runner/options/cloud-runner-options-reader';
|
import OrchestratorOptionsReader from '../orchestrator/options/orchestrator-options-reader';
|
||||||
import GitHub from '../github';
|
import GitHub from '../github';
|
||||||
import { CloudRunnerFolders } from '../cloud-runner/options/cloud-runner-folders';
|
|
||||||
import { CloudRunnerSystem } from '../cloud-runner/services/core/cloud-runner-system';
|
|
||||||
import { OptionValues } from 'commander';
|
import { OptionValues } from 'commander';
|
||||||
import { InputKey } from '../input';
|
import { InputKey } from '../input';
|
||||||
|
|
||||||
@@ -38,7 +36,7 @@ export class Cli {
|
|||||||
const program = new Command();
|
const program = new Command();
|
||||||
program.version('0.0.1');
|
program.version('0.0.1');
|
||||||
|
|
||||||
const properties = CloudRunnerOptionsReader.GetProperties();
|
const properties = OrchestratorOptionsReader.GetProperties();
|
||||||
const actionYamlReader: ActionYamlReader = new ActionYamlReader();
|
const actionYamlReader: ActionYamlReader = new ActionYamlReader();
|
||||||
for (const element of properties) {
|
for (const element of properties) {
|
||||||
program.option(`--${element} <${element}>`, actionYamlReader.GetActionYamlValue(element));
|
program.option(`--${element} <${element}>`, actionYamlReader.GetActionYamlValue(element));
|
||||||
@@ -54,6 +52,7 @@ export class Cli {
|
|||||||
program.option('--cachePushTo <cachePushTo>', 'cache push to caching folder');
|
program.option('--cachePushTo <cachePushTo>', 'cache push to caching folder');
|
||||||
program.option('--artifactName <artifactName>', 'caching artifact name');
|
program.option('--artifactName <artifactName>', 'caching artifact name');
|
||||||
program.option('--select <select>', 'select a particular resource');
|
program.option('--select <select>', 'select a particular resource');
|
||||||
|
program.option('--logFile <logFile>', 'output to log file (log stream only)');
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
Cli.options = program.opts();
|
Cli.options = program.opts();
|
||||||
|
|
||||||
@@ -63,23 +62,23 @@ export class Cli {
|
|||||||
static async RunCli(): Promise<void> {
|
static async RunCli(): Promise<void> {
|
||||||
GitHub.githubInputEnabled = false;
|
GitHub.githubInputEnabled = false;
|
||||||
if (Cli.options!['populateOverride'] === `true`) {
|
if (Cli.options!['populateOverride'] === `true`) {
|
||||||
await CloudRunnerQueryOverride.PopulateQueryOverrideInput();
|
await OrchestratorQueryOverride.PopulateQueryOverrideInput();
|
||||||
}
|
}
|
||||||
if (Cli.options!['logInput']) {
|
if (Cli.options!['logInput']) {
|
||||||
Cli.logInput();
|
Cli.logInput();
|
||||||
}
|
}
|
||||||
const results = CliFunctionsRepository.GetCliFunctions(Cli.options?.mode);
|
const results = CliFunctionsRepository.GetCliFunctions(Cli.options?.mode);
|
||||||
CloudRunnerLogger.log(`Entrypoint: ${results.key}`);
|
OrchestratorLogger.log(`Entrypoint: ${results.key}`);
|
||||||
Cli.options!.versioning = 'None';
|
Cli.options!.versioning = 'None';
|
||||||
|
|
||||||
CloudRunner.buildParameters = await BuildParameters.create();
|
Orchestrator.buildParameters = await BuildParameters.create();
|
||||||
CloudRunner.buildParameters.buildGuid = process.env.BUILD_GUID || ``;
|
Orchestrator.buildParameters.buildGuid = process.env.BUILD_GUID || ``;
|
||||||
CloudRunnerLogger.log(`Build Params:
|
OrchestratorLogger.log(`Build Params:
|
||||||
${JSON.stringify(CloudRunner.buildParameters, undefined, 4)}
|
${JSON.stringify(Orchestrator.buildParameters, undefined, 4)}
|
||||||
`);
|
`);
|
||||||
CloudRunner.lockedWorkspace = process.env.LOCKED_WORKSPACE || ``;
|
Orchestrator.lockedWorkspace = process.env.LOCKED_WORKSPACE || ``;
|
||||||
CloudRunnerLogger.log(`Locked Workspace: ${CloudRunner.lockedWorkspace}`);
|
OrchestratorLogger.log(`Locked Workspace: ${Orchestrator.lockedWorkspace}`);
|
||||||
await CloudRunner.setup(CloudRunner.buildParameters);
|
await Orchestrator.setup(Orchestrator.buildParameters);
|
||||||
|
|
||||||
return await results.target[results.propertyKey](Cli.options);
|
return await results.target[results.propertyKey](Cli.options);
|
||||||
}
|
}
|
||||||
@@ -88,7 +87,7 @@ export class Cli {
|
|||||||
private static logInput() {
|
private static logInput() {
|
||||||
core.info(`\n`);
|
core.info(`\n`);
|
||||||
core.info(`INPUT:`);
|
core.info(`INPUT:`);
|
||||||
const properties = CloudRunnerOptionsReader.GetProperties();
|
const properties = OrchestratorOptionsReader.GetProperties();
|
||||||
for (const element of properties) {
|
for (const element of properties) {
|
||||||
if (
|
if (
|
||||||
element in Input &&
|
element in Input &&
|
||||||
@@ -105,28 +104,28 @@ export class Cli {
|
|||||||
core.info(`\n`);
|
core.info(`\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CliFunction(`cli-build`, `runs a cloud runner build`)
|
@CliFunction(`cli-build`, `runs a orchestrator build`)
|
||||||
public static async CLIBuild(): Promise<string> {
|
public static async CLIBuild(): Promise<string> {
|
||||||
const buildParameter = await BuildParameters.create();
|
const buildParameter = await BuildParameters.create();
|
||||||
const baseImage = new ImageTag(buildParameter);
|
const baseImage = new ImageTag(buildParameter);
|
||||||
|
|
||||||
return await CloudRunner.run(buildParameter, baseImage.toString());
|
return (await Orchestrator.run(buildParameter, baseImage.toString())).BuildResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@CliFunction(`async-workflow`, `runs a cloud runner build`)
|
@CliFunction(`async-workflow`, `runs a orchestrator build`)
|
||||||
public static async asyncronousWorkflow(): Promise<string> {
|
public static async asyncronousWorkflow(): Promise<string> {
|
||||||
const buildParameter = await BuildParameters.create();
|
const buildParameter = await BuildParameters.create();
|
||||||
const baseImage = new ImageTag(buildParameter);
|
const baseImage = new ImageTag(buildParameter);
|
||||||
await CloudRunner.setup(buildParameter);
|
await Orchestrator.setup(buildParameter);
|
||||||
|
|
||||||
return await CloudRunner.run(buildParameter, baseImage.toString());
|
return (await Orchestrator.run(buildParameter, baseImage.toString())).BuildResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@CliFunction(`checks-update`, `runs a cloud runner build`)
|
@CliFunction(`checks-update`, `runs a orchestrator build`)
|
||||||
public static async checksUpdate() {
|
public static async checksUpdate() {
|
||||||
const buildParameter = await BuildParameters.create();
|
const buildParameter = await BuildParameters.create();
|
||||||
|
|
||||||
await CloudRunner.setup(buildParameter);
|
await Orchestrator.setup(buildParameter);
|
||||||
const input = JSON.parse(process.env.CHECKS_UPDATE || ``);
|
const input = JSON.parse(process.env.CHECKS_UPDATE || ``);
|
||||||
core.info(`Checks Update ${process.env.CHECKS_UPDATE}`);
|
core.info(`Checks Update ${process.env.CHECKS_UPDATE}`);
|
||||||
if (input.mode === `create`) {
|
if (input.mode === `create`) {
|
||||||
@@ -140,18 +139,18 @@ export class Cli {
|
|||||||
public static async GarbageCollect(): Promise<string> {
|
public static async GarbageCollect(): Promise<string> {
|
||||||
const buildParameter = await BuildParameters.create();
|
const buildParameter = await BuildParameters.create();
|
||||||
|
|
||||||
await CloudRunner.setup(buildParameter);
|
await Orchestrator.setup(buildParameter);
|
||||||
|
|
||||||
return await CloudRunner.Provider.garbageCollect(``, false, 0, false, false);
|
return await Orchestrator.Provider.garbageCollect(``, false, 0, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CliFunction(`list-resources`, `lists active resources`)
|
@CliFunction(`list-resources`, `lists active resources`)
|
||||||
public static async ListResources(): Promise<string[]> {
|
public static async ListResources(): Promise<string[]> {
|
||||||
const buildParameter = await BuildParameters.create();
|
const buildParameter = await BuildParameters.create();
|
||||||
|
|
||||||
await CloudRunner.setup(buildParameter);
|
await Orchestrator.setup(buildParameter);
|
||||||
const result = await CloudRunner.Provider.listResources();
|
const result = await Orchestrator.Provider.listResources();
|
||||||
CloudRunnerLogger.log(JSON.stringify(result, undefined, 4));
|
OrchestratorLogger.log(JSON.stringify(result, undefined, 4));
|
||||||
|
|
||||||
return result.map((x) => x.Name);
|
return result.map((x) => x.Name);
|
||||||
}
|
}
|
||||||
@@ -160,44 +159,17 @@ export class Cli {
|
|||||||
public static async ListWorfklow(): Promise<string[]> {
|
public static async ListWorfklow(): Promise<string[]> {
|
||||||
const buildParameter = await BuildParameters.create();
|
const buildParameter = await BuildParameters.create();
|
||||||
|
|
||||||
await CloudRunner.setup(buildParameter);
|
await Orchestrator.setup(buildParameter);
|
||||||
|
|
||||||
return (await CloudRunner.Provider.listWorkflow()).map((x) => x.Name);
|
return (await Orchestrator.Provider.listWorkflow()).map((x) => x.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CliFunction(`watch`, `follows logs of a running workflow`)
|
@CliFunction(`watch`, `follows logs of a running workflow`)
|
||||||
public static async Watch(): Promise<string> {
|
public static async Watch(): Promise<string> {
|
||||||
const buildParameter = await BuildParameters.create();
|
const buildParameter = await BuildParameters.create();
|
||||||
|
|
||||||
await CloudRunner.setup(buildParameter);
|
await Orchestrator.setup(buildParameter);
|
||||||
|
|
||||||
return await CloudRunner.Provider.watchWorkflow();
|
return await Orchestrator.Provider.watchWorkflow();
|
||||||
}
|
|
||||||
|
|
||||||
@CliFunction(`remote-cli-post-build`, `runs a cloud runner build`)
|
|
||||||
public static async PostCLIBuild(): Promise<string> {
|
|
||||||
core.info(`Running POST build tasks`);
|
|
||||||
|
|
||||||
await Caching.PushToCache(
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(`${CloudRunnerFolders.cacheFolderForCacheKeyFull}/Library`),
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.libraryFolderAbsolute),
|
|
||||||
`lib-${CloudRunner.buildParameters.buildGuid}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
await Caching.PushToCache(
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(`${CloudRunnerFolders.cacheFolderForCacheKeyFull}/build`),
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectBuildFolderAbsolute),
|
|
||||||
`build-${CloudRunner.buildParameters.buildGuid}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!BuildParameters.shouldUseRetainedWorkspaceMode(CloudRunner.buildParameters)) {
|
|
||||||
await CloudRunnerSystem.Run(
|
|
||||||
`rm -r ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await RemoteClient.runCustomHookFiles(`after-build`);
|
|
||||||
|
|
||||||
return new Promise((result) => result(``));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
import AwsBuildPlatform from './providers/aws';
|
|
||||||
import { BuildParameters, Input } from '..';
|
|
||||||
import Kubernetes from './providers/k8s';
|
|
||||||
import CloudRunnerLogger from './services/core/cloud-runner-logger';
|
|
||||||
import { CloudRunnerStepParameters } from './options/cloud-runner-step-parameters';
|
|
||||||
import { WorkflowCompositionRoot } from './workflows/workflow-composition-root';
|
|
||||||
import { CloudRunnerError } from './error/cloud-runner-error';
|
|
||||||
import { TaskParameterSerializer } from './services/core/task-parameter-serializer';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import CloudRunnerSecret from './options/cloud-runner-secret';
|
|
||||||
import { ProviderInterface } from './providers/provider-interface';
|
|
||||||
import CloudRunnerEnvironmentVariable from './options/cloud-runner-environment-variable';
|
|
||||||
import TestCloudRunner from './providers/test';
|
|
||||||
import LocalCloudRunner from './providers/local';
|
|
||||||
import LocalDockerCloudRunner from './providers/docker';
|
|
||||||
import GitHub from '../github';
|
|
||||||
import SharedWorkspaceLocking from './services/core/shared-workspace-locking';
|
|
||||||
import { FollowLogStreamService } from './services/core/follow-log-stream-service';
|
|
||||||
|
|
||||||
class CloudRunner {
|
|
||||||
public static Provider: ProviderInterface;
|
|
||||||
public static buildParameters: BuildParameters;
|
|
||||||
private static defaultSecrets: CloudRunnerSecret[];
|
|
||||||
private static cloudRunnerEnvironmentVariables: CloudRunnerEnvironmentVariable[];
|
|
||||||
static lockedWorkspace: string = ``;
|
|
||||||
public static readonly retainedWorkspacePrefix: string = `retained-workspace`;
|
|
||||||
public static get isCloudRunnerEnvironment() {
|
|
||||||
return process.env[`GITHUB_ACTIONS`] !== `true`;
|
|
||||||
}
|
|
||||||
public static get isCloudRunnerAsyncEnvironment() {
|
|
||||||
return process.env[`ASYNC_WORKFLOW`] === `true`;
|
|
||||||
}
|
|
||||||
public static async setup(buildParameters: BuildParameters) {
|
|
||||||
CloudRunnerLogger.setup();
|
|
||||||
CloudRunnerLogger.log(`Setting up cloud runner`);
|
|
||||||
CloudRunner.buildParameters = buildParameters;
|
|
||||||
if (CloudRunner.buildParameters.githubCheckId === ``) {
|
|
||||||
CloudRunner.buildParameters.githubCheckId = await GitHub.createGitHubCheck(CloudRunner.buildParameters.buildGuid);
|
|
||||||
}
|
|
||||||
CloudRunner.setupSelectedBuildPlatform();
|
|
||||||
CloudRunner.defaultSecrets = TaskParameterSerializer.readDefaultSecrets();
|
|
||||||
CloudRunner.cloudRunnerEnvironmentVariables =
|
|
||||||
TaskParameterSerializer.createCloudRunnerEnvironmentVariables(buildParameters);
|
|
||||||
if (GitHub.githubInputEnabled) {
|
|
||||||
const buildParameterPropertyNames = Object.getOwnPropertyNames(buildParameters);
|
|
||||||
for (const element of CloudRunner.cloudRunnerEnvironmentVariables) {
|
|
||||||
// CloudRunnerLogger.log(`Cloud Runner output ${Input.ToEnvVarFormat(element.name)} = ${element.value}`);
|
|
||||||
core.setOutput(Input.ToEnvVarFormat(element.name), element.value);
|
|
||||||
}
|
|
||||||
for (const element of buildParameterPropertyNames) {
|
|
||||||
// CloudRunnerLogger.log(`Cloud Runner output ${Input.ToEnvVarFormat(element)} = ${buildParameters[element]}`);
|
|
||||||
core.setOutput(Input.ToEnvVarFormat(element), buildParameters[element]);
|
|
||||||
}
|
|
||||||
core.setOutput(
|
|
||||||
Input.ToEnvVarFormat(`buildArtifact`),
|
|
||||||
`build-${CloudRunner.buildParameters.buildGuid}.tar${
|
|
||||||
CloudRunner.buildParameters.useCompressionStrategy ? '.lz4' : ''
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FollowLogStreamService.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static setupSelectedBuildPlatform() {
|
|
||||||
CloudRunnerLogger.log(`Cloud Runner platform selected ${CloudRunner.buildParameters.providerStrategy}`);
|
|
||||||
switch (CloudRunner.buildParameters.providerStrategy) {
|
|
||||||
case 'k8s':
|
|
||||||
CloudRunner.Provider = new Kubernetes(CloudRunner.buildParameters);
|
|
||||||
break;
|
|
||||||
case 'aws':
|
|
||||||
CloudRunner.Provider = new AwsBuildPlatform(CloudRunner.buildParameters);
|
|
||||||
break;
|
|
||||||
case 'test':
|
|
||||||
CloudRunner.Provider = new TestCloudRunner();
|
|
||||||
break;
|
|
||||||
case 'local-docker':
|
|
||||||
CloudRunner.Provider = new LocalDockerCloudRunner();
|
|
||||||
break;
|
|
||||||
case 'local-system':
|
|
||||||
CloudRunner.Provider = new LocalCloudRunner();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async run(buildParameters: BuildParameters, baseImage: string) {
|
|
||||||
await CloudRunner.setup(buildParameters);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('Setup shared cloud runner resources');
|
|
||||||
await CloudRunner.Provider.setupWorkflow(
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
CloudRunner.buildParameters,
|
|
||||||
CloudRunner.buildParameters.branch,
|
|
||||||
CloudRunner.defaultSecrets,
|
|
||||||
);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
|
|
||||||
try {
|
|
||||||
if (buildParameters.maxRetainedWorkspaces > 0) {
|
|
||||||
CloudRunner.lockedWorkspace = SharedWorkspaceLocking.NewWorkspaceName();
|
|
||||||
|
|
||||||
const result = await SharedWorkspaceLocking.GetLockedWorkspace(
|
|
||||||
CloudRunner.lockedWorkspace,
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
CloudRunner.buildParameters,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
CloudRunnerLogger.logLine(`Using retained workspace ${CloudRunner.lockedWorkspace}`);
|
|
||||||
CloudRunner.cloudRunnerEnvironmentVariables = [
|
|
||||||
...CloudRunner.cloudRunnerEnvironmentVariables,
|
|
||||||
{ name: `LOCKED_WORKSPACE`, value: CloudRunner.lockedWorkspace },
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
CloudRunnerLogger.log(`Max retained workspaces reached ${buildParameters.maxRetainedWorkspaces}`);
|
|
||||||
buildParameters.maxRetainedWorkspaces = 0;
|
|
||||||
CloudRunner.lockedWorkspace = ``;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const content = { ...CloudRunner.buildParameters };
|
|
||||||
content.gitPrivateToken = ``;
|
|
||||||
content.unitySerial = ``;
|
|
||||||
const jsonContent = JSON.stringify(content, undefined, 4);
|
|
||||||
await GitHub.updateGitHubCheck(jsonContent, CloudRunner.buildParameters.buildGuid);
|
|
||||||
const output = await new WorkflowCompositionRoot().run(
|
|
||||||
new CloudRunnerStepParameters(
|
|
||||||
baseImage,
|
|
||||||
CloudRunner.cloudRunnerEnvironmentVariables,
|
|
||||||
CloudRunner.defaultSecrets,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('Cleanup shared cloud runner resources');
|
|
||||||
await CloudRunner.Provider.cleanupWorkflow(
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
CloudRunner.buildParameters,
|
|
||||||
CloudRunner.buildParameters.branch,
|
|
||||||
CloudRunner.defaultSecrets,
|
|
||||||
);
|
|
||||||
CloudRunnerLogger.log(`Cleanup complete`);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
|
|
||||||
await GitHub.updateGitHubCheck(CloudRunner.buildParameters.buildGuid, `success`, `success`, `completed`);
|
|
||||||
|
|
||||||
if (BuildParameters.shouldUseRetainedWorkspaceMode(buildParameters)) {
|
|
||||||
const workspace = CloudRunner.lockedWorkspace || ``;
|
|
||||||
await SharedWorkspaceLocking.ReleaseWorkspace(
|
|
||||||
workspace,
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
CloudRunner.buildParameters,
|
|
||||||
);
|
|
||||||
const isLocked = await SharedWorkspaceLocking.IsWorkspaceLocked(workspace, CloudRunner.buildParameters);
|
|
||||||
if (isLocked) {
|
|
||||||
throw new Error(
|
|
||||||
`still locked after releasing ${await SharedWorkspaceLocking.GetAllLocksForWorkspace(
|
|
||||||
workspace,
|
|
||||||
buildParameters,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
CloudRunner.lockedWorkspace = ``;
|
|
||||||
}
|
|
||||||
|
|
||||||
await GitHub.triggerWorkflowOnComplete(CloudRunner.buildParameters.finalHooks);
|
|
||||||
|
|
||||||
if (buildParameters.constantGarbageCollection) {
|
|
||||||
CloudRunner.Provider.garbageCollect(``, true, buildParameters.garbageMaxAge, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
} catch (error: any) {
|
|
||||||
CloudRunnerLogger.log(JSON.stringify(error, undefined, 4));
|
|
||||||
await GitHub.updateGitHubCheck(
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
`Failed - Error ${error?.message || error}`,
|
|
||||||
`failure`,
|
|
||||||
`completed`,
|
|
||||||
);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
|
|
||||||
await CloudRunnerError.handleException(error, CloudRunner.buildParameters, CloudRunner.defaultSecrets);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default CloudRunner;
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
import CloudRunnerSecret from '../options/cloud-runner-secret';
|
|
||||||
import BuildParameters from '../../build-parameters';
|
|
||||||
|
|
||||||
export class CloudRunnerError {
|
|
||||||
public static async handleException(error: unknown, buildParameters: BuildParameters, secrets: CloudRunnerSecret[]) {
|
|
||||||
CloudRunnerLogger.error(JSON.stringify(error, undefined, 4));
|
|
||||||
core.setFailed('Cloud Runner failed');
|
|
||||||
if (CloudRunner.Provider !== undefined) {
|
|
||||||
await CloudRunner.Provider.cleanupWorkflow(
|
|
||||||
buildParameters.buildGuid,
|
|
||||||
buildParameters,
|
|
||||||
buildParameters.branch,
|
|
||||||
secrets,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
class CloudRunnerConstants {
|
|
||||||
static alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
||||||
}
|
|
||||||
export default CloudRunnerConstants;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
class CloudRunnerEnvironmentVariable {
|
|
||||||
public name!: string;
|
|
||||||
public value!: string;
|
|
||||||
}
|
|
||||||
export default CloudRunnerEnvironmentVariable;
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import path from 'node:path';
|
|
||||||
import CloudRunnerOptions from './cloud-runner-options';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
import BuildParameters from '../../build-parameters';
|
|
||||||
|
|
||||||
export class CloudRunnerFolders {
|
|
||||||
public static readonly repositoryFolder = 'repo';
|
|
||||||
|
|
||||||
public static ToLinuxFolder(folder: string) {
|
|
||||||
return folder.replace(/\\/g, `/`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only the following paths that do not start a path.join with another "Full" suffixed property need to start with an absolute /
|
|
||||||
|
|
||||||
public static get uniqueCloudRunnerJobFolderAbsolute(): string {
|
|
||||||
return CloudRunner.buildParameters && BuildParameters.shouldUseRetainedWorkspaceMode(CloudRunner.buildParameters)
|
|
||||||
? path.join(`/`, CloudRunnerFolders.buildVolumeFolder, CloudRunner.lockedWorkspace)
|
|
||||||
: path.join(`/`, CloudRunnerFolders.buildVolumeFolder, CloudRunner.buildParameters.buildGuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get cacheFolderForAllFull(): string {
|
|
||||||
return path.join('/', CloudRunnerFolders.buildVolumeFolder, CloudRunnerFolders.cacheFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get cacheFolderForCacheKeyFull(): string {
|
|
||||||
return path.join(
|
|
||||||
'/',
|
|
||||||
CloudRunnerFolders.buildVolumeFolder,
|
|
||||||
CloudRunnerFolders.cacheFolder,
|
|
||||||
CloudRunner.buildParameters.cacheKey,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get builderPathAbsolute(): string {
|
|
||||||
return path.join(
|
|
||||||
CloudRunnerOptions.useSharedBuilder
|
|
||||||
? `/${CloudRunnerFolders.buildVolumeFolder}`
|
|
||||||
: CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute,
|
|
||||||
`builder`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get repoPathAbsolute(): string {
|
|
||||||
return path.join(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute, CloudRunnerFolders.repositoryFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get projectPathAbsolute(): string {
|
|
||||||
return path.join(CloudRunnerFolders.repoPathAbsolute, CloudRunner.buildParameters.projectPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get libraryFolderAbsolute(): string {
|
|
||||||
return path.join(CloudRunnerFolders.projectPathAbsolute, `Library`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get projectBuildFolderAbsolute(): string {
|
|
||||||
return path.join(CloudRunnerFolders.repoPathAbsolute, CloudRunner.buildParameters.buildPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get lfsFolderAbsolute(): string {
|
|
||||||
return path.join(CloudRunnerFolders.repoPathAbsolute, `.git`, `lfs`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get purgeRemoteCaching(): boolean {
|
|
||||||
return process.env.PURGE_REMOTE_BUILDER_CACHE !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get lfsCacheFolderFull() {
|
|
||||||
return path.join(CloudRunnerFolders.cacheFolderForCacheKeyFull, `lfs`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get libraryCacheFolderFull() {
|
|
||||||
return path.join(CloudRunnerFolders.cacheFolderForCacheKeyFull, `Library`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get unityBuilderRepoUrl(): string {
|
|
||||||
return `https://${CloudRunner.buildParameters.gitPrivateToken}@github.com/game-ci/unity-builder.git`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get targetBuildRepoUrl(): string {
|
|
||||||
return `https://${CloudRunner.buildParameters.gitPrivateToken}@github.com/${CloudRunner.buildParameters.githubRepo}.git`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get buildVolumeFolder() {
|
|
||||||
return 'data';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get cacheFolder() {
|
|
||||||
return 'cache';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import Input from '../../input';
|
|
||||||
import CloudRunnerOptions from './cloud-runner-options';
|
|
||||||
|
|
||||||
class CloudRunnerOptionsReader {
|
|
||||||
static GetProperties() {
|
|
||||||
return [...Object.getOwnPropertyNames(Input), ...Object.getOwnPropertyNames(CloudRunnerOptions)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CloudRunnerOptionsReader;
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
import { Cli } from '../../cli/cli';
|
|
||||||
import CloudRunnerQueryOverride from './cloud-runner-query-override';
|
|
||||||
import GitHub from '../../github';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
|
|
||||||
class CloudRunnerOptions {
|
|
||||||
// ### ### ###
|
|
||||||
// Input Handling
|
|
||||||
// ### ### ###
|
|
||||||
public static getInput(query: string): string | undefined {
|
|
||||||
if (GitHub.githubInputEnabled) {
|
|
||||||
const coreInput = core.getInput(query);
|
|
||||||
if (coreInput && coreInput !== '') {
|
|
||||||
return coreInput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const alternativeQuery = CloudRunnerOptions.ToEnvVarFormat(query);
|
|
||||||
|
|
||||||
// Query input sources
|
|
||||||
if (Cli.query(query, alternativeQuery)) {
|
|
||||||
return Cli.query(query, alternativeQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CloudRunnerQueryOverride.query(query, alternativeQuery)) {
|
|
||||||
return CloudRunnerQueryOverride.query(query, alternativeQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env[query] !== undefined) {
|
|
||||||
return process.env[query];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alternativeQuery !== query && process.env[alternativeQuery] !== undefined) {
|
|
||||||
return process.env[alternativeQuery];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ToEnvVarFormat(input: string): string {
|
|
||||||
if (input.toUpperCase() === input) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
return input
|
|
||||||
.replace(/([A-Z])/g, ' $1')
|
|
||||||
.trim()
|
|
||||||
.toUpperCase()
|
|
||||||
.replace(/ /g, '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Provider parameters
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get region(): string {
|
|
||||||
return CloudRunnerOptions.getInput('region') || 'eu-west-2';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// GitHub parameters
|
|
||||||
// ### ### ###
|
|
||||||
static get githubChecks(): boolean {
|
|
||||||
const value = CloudRunnerOptions.getInput('githubChecks');
|
|
||||||
|
|
||||||
return value === `true` || false;
|
|
||||||
}
|
|
||||||
static get githubCheckId(): string {
|
|
||||||
return CloudRunnerOptions.getInput('githubCheckId') || ``;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get githubOwner(): string {
|
|
||||||
return CloudRunnerOptions.getInput('githubOwner') || CloudRunnerOptions.githubRepo?.split(`/`)[0] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get githubRepoName(): string {
|
|
||||||
return CloudRunnerOptions.getInput('githubRepoName') || CloudRunnerOptions.githubRepo?.split(`/`)[1] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get finalHooks(): string[] {
|
|
||||||
return CloudRunnerOptions.getInput('finalHooks')?.split(',') || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Git syncronization parameters
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get githubRepo(): string | undefined {
|
|
||||||
return CloudRunnerOptions.getInput('GITHUB_REPOSITORY') || CloudRunnerOptions.getInput('GITHUB_REPO') || undefined;
|
|
||||||
}
|
|
||||||
static get branch(): string {
|
|
||||||
if (CloudRunnerOptions.getInput(`GITHUB_REF`)) {
|
|
||||||
return (
|
|
||||||
CloudRunnerOptions.getInput(`GITHUB_REF`)?.replace('refs/', '').replace(`head/`, '').replace(`heads/`, '') || ``
|
|
||||||
);
|
|
||||||
} else if (CloudRunnerOptions.getInput('branch')) {
|
|
||||||
return CloudRunnerOptions.getInput('branch') || ``;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Cloud Runner parameters
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get buildPlatform(): string {
|
|
||||||
const input = CloudRunnerOptions.getInput('buildPlatform');
|
|
||||||
if (input) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
if (CloudRunnerOptions.providerStrategy !== 'local') {
|
|
||||||
return 'linux';
|
|
||||||
}
|
|
||||||
|
|
||||||
return ``;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get cloudRunnerBranch(): string {
|
|
||||||
return CloudRunnerOptions.getInput('cloudRunnerBranch') || 'main';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get providerStrategy(): string {
|
|
||||||
const provider =
|
|
||||||
CloudRunnerOptions.getInput('cloudRunnerCluster') || CloudRunnerOptions.getInput('providerStrategy');
|
|
||||||
if (Cli.isCliMode) {
|
|
||||||
return provider || 'aws';
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider || 'local';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get containerCpu(): string {
|
|
||||||
return CloudRunnerOptions.getInput('containerCpu') || `1024`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get containerMemory(): string {
|
|
||||||
return CloudRunnerOptions.getInput('containerMemory') || `3072`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get customJob(): string {
|
|
||||||
return CloudRunnerOptions.getInput('customJob') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Custom commands from files parameters
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get containerHookFiles(): string[] {
|
|
||||||
return CloudRunnerOptions.getInput('containerHookFiles')?.split(`,`) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static get commandHookFiles(): string[] {
|
|
||||||
return CloudRunnerOptions.getInput('commandHookFiles')?.split(`,`) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Custom commands from yaml parameters
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get commandHooks(): string {
|
|
||||||
return CloudRunnerOptions.getInput('commandHooks') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get postBuildContainerHooks(): string {
|
|
||||||
return CloudRunnerOptions.getInput('postBuildContainerHooks') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get preBuildContainerHooks(): string {
|
|
||||||
return CloudRunnerOptions.getInput('preBuildContainerHooks') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Input override handling
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get pullInputList(): string[] {
|
|
||||||
return CloudRunnerOptions.getInput('pullInputList')?.split(`,`) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static get inputPullCommand(): string {
|
|
||||||
const value = CloudRunnerOptions.getInput('inputPullCommand');
|
|
||||||
|
|
||||||
if (value === 'gcp-secret-manager') {
|
|
||||||
return 'gcloud secrets versions access 1 --secret="{0}"';
|
|
||||||
} else if (value === 'aws-secret-manager') {
|
|
||||||
return 'aws secretsmanager get-secret-value --secret-id {0}';
|
|
||||||
}
|
|
||||||
|
|
||||||
return value || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Aws
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get awsStackName() {
|
|
||||||
return CloudRunnerOptions.getInput('awsStackName') || 'game-ci';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// K8s
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get kubeConfig(): string {
|
|
||||||
return CloudRunnerOptions.getInput('kubeConfig') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get kubeVolume(): string {
|
|
||||||
return CloudRunnerOptions.getInput('kubeVolume') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get kubeVolumeSize(): string {
|
|
||||||
return CloudRunnerOptions.getInput('kubeVolumeSize') || '25Gi';
|
|
||||||
}
|
|
||||||
|
|
||||||
static get kubeStorageClass(): string {
|
|
||||||
return CloudRunnerOptions.getInput('kubeStorageClass') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Caching
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get cacheKey(): string {
|
|
||||||
return CloudRunnerOptions.getInput('cacheKey') || CloudRunnerOptions.branch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Utility Parameters
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get cloudRunnerDebug(): boolean {
|
|
||||||
return (
|
|
||||||
CloudRunnerOptions.getInput(`cloudRunnerTests`) === `true` ||
|
|
||||||
CloudRunnerOptions.getInput(`cloudRunnerDebug`) === `true` ||
|
|
||||||
CloudRunnerOptions.getInput(`cloudRunnerDebugTree`) === `true` ||
|
|
||||||
CloudRunnerOptions.getInput(`cloudRunnerDebugEnv`) === `true` ||
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
static get skipLfs(): boolean {
|
|
||||||
return CloudRunnerOptions.getInput(`skipLfs`) === `true`;
|
|
||||||
}
|
|
||||||
static get skipCache(): boolean {
|
|
||||||
return CloudRunnerOptions.getInput(`skipCache`) === `true`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get asyncCloudRunner(): boolean {
|
|
||||||
return CloudRunnerOptions.getInput('asyncCloudRunner') === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get useLargePackages(): boolean {
|
|
||||||
return CloudRunnerOptions.getInput(`useLargePackages`) === `true`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get useSharedBuilder(): boolean {
|
|
||||||
return CloudRunnerOptions.getInput(`useSharedBuilder`) === `true`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get useCompressionStrategy(): boolean {
|
|
||||||
return CloudRunnerOptions.getInput(`useCompressionStrategy`) === `true`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get useCleanupCron(): boolean {
|
|
||||||
return (CloudRunnerOptions.getInput(`useCleanupCron`) || 'true') === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Retained Workspace
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
public static get maxRetainedWorkspaces(): string {
|
|
||||||
return CloudRunnerOptions.getInput(`maxRetainedWorkspaces`) || `0`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ### ### ###
|
|
||||||
// Garbage Collection
|
|
||||||
// ### ### ###
|
|
||||||
|
|
||||||
static get garbageMaxAge(): number {
|
|
||||||
return Number(CloudRunnerOptions.getInput(`garbageMaxAge`)) || 24;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CloudRunnerOptions;
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import Input from '../../input';
|
|
||||||
import { GenericInputReader } from '../../input-readers/generic-input-reader';
|
|
||||||
import CloudRunnerOptions from './cloud-runner-options';
|
|
||||||
|
|
||||||
const formatFunction = (value: string, arguments_: any[]) => {
|
|
||||||
for (const element of arguments_) {
|
|
||||||
value = value.replace(`{${element.key}}`, element.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CloudRunnerQueryOverride {
|
|
||||||
static queryOverrides: { [key: string]: string } | undefined;
|
|
||||||
|
|
||||||
// TODO accept premade secret sources or custom secret source definition yamls
|
|
||||||
|
|
||||||
public static query(key: string, alternativeKey: string) {
|
|
||||||
if (CloudRunnerQueryOverride.queryOverrides && CloudRunnerQueryOverride.queryOverrides[key] !== undefined) {
|
|
||||||
return CloudRunnerQueryOverride.queryOverrides[key];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
CloudRunnerQueryOverride.queryOverrides &&
|
|
||||||
alternativeKey &&
|
|
||||||
CloudRunnerQueryOverride.queryOverrides[alternativeKey] !== undefined
|
|
||||||
) {
|
|
||||||
return CloudRunnerQueryOverride.queryOverrides[alternativeKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static shouldUseOverride(query: string) {
|
|
||||||
if (CloudRunnerOptions.inputPullCommand !== '') {
|
|
||||||
if (CloudRunnerOptions.pullInputList.length > 0) {
|
|
||||||
const doesInclude =
|
|
||||||
CloudRunnerOptions.pullInputList.includes(query) ||
|
|
||||||
CloudRunnerOptions.pullInputList.includes(Input.ToEnvVarFormat(query));
|
|
||||||
|
|
||||||
return doesInclude ? true : false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async queryOverride(query: string) {
|
|
||||||
if (!this.shouldUseOverride(query)) {
|
|
||||||
throw new Error(`Should not be trying to run override query on ${query}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await GenericInputReader.Run(
|
|
||||||
formatFunction(CloudRunnerOptions.inputPullCommand, [{ key: 0, value: query }]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async PopulateQueryOverrideInput() {
|
|
||||||
const queries = CloudRunnerOptions.pullInputList;
|
|
||||||
CloudRunnerQueryOverride.queryOverrides = {};
|
|
||||||
for (const element of queries) {
|
|
||||||
if (CloudRunnerQueryOverride.shouldUseOverride(element)) {
|
|
||||||
CloudRunnerQueryOverride.queryOverrides[element] = await CloudRunnerQueryOverride.queryOverride(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default CloudRunnerQueryOverride;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export class CloudRunnerStatics {
|
|
||||||
public static readonly logPrefix = `Cloud-Runner`;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import CloudRunnerEnvironmentVariable from './cloud-runner-environment-variable';
|
|
||||||
import CloudRunnerSecret from './cloud-runner-secret';
|
|
||||||
|
|
||||||
export class CloudRunnerStepParameters {
|
|
||||||
public image: string;
|
|
||||||
public environment: CloudRunnerEnvironmentVariable[];
|
|
||||||
public secrets: CloudRunnerSecret[];
|
|
||||||
constructor(image: string, environmentVariables: CloudRunnerEnvironmentVariable[], secrets: CloudRunnerSecret[]) {
|
|
||||||
this.image = image;
|
|
||||||
this.environment = environmentVariables;
|
|
||||||
this.secrets = secrets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import * as SDK from 'aws-sdk';
|
|
||||||
import { BaseStackFormation } from './cloud-formations/base-stack-formation';
|
|
||||||
import crypto from 'node:crypto';
|
|
||||||
|
|
||||||
export class AWSBaseStack {
|
|
||||||
constructor(baseStackName: string) {
|
|
||||||
this.baseStackName = baseStackName;
|
|
||||||
}
|
|
||||||
private baseStackName: string;
|
|
||||||
|
|
||||||
async setupBaseStack(CF: SDK.CloudFormation) {
|
|
||||||
const baseStackName = this.baseStackName;
|
|
||||||
|
|
||||||
const baseStack = BaseStackFormation.formation;
|
|
||||||
|
|
||||||
// Cloud Formation Input
|
|
||||||
const describeStackInput: SDK.CloudFormation.DescribeStacksInput = {
|
|
||||||
StackName: baseStackName,
|
|
||||||
};
|
|
||||||
const parametersWithoutHash: SDK.CloudFormation.Parameter[] = [
|
|
||||||
{ ParameterKey: 'EnvironmentName', ParameterValue: baseStackName },
|
|
||||||
];
|
|
||||||
const parametersHash = crypto
|
|
||||||
.createHash('md5')
|
|
||||||
.update(baseStack + JSON.stringify(parametersWithoutHash))
|
|
||||||
.digest('hex');
|
|
||||||
const parameters: SDK.CloudFormation.Parameter[] = [
|
|
||||||
...parametersWithoutHash,
|
|
||||||
...[{ ParameterKey: 'Version', ParameterValue: parametersHash }],
|
|
||||||
];
|
|
||||||
const updateInput: SDK.CloudFormation.UpdateStackInput = {
|
|
||||||
StackName: baseStackName,
|
|
||||||
TemplateBody: baseStack,
|
|
||||||
Parameters: parameters,
|
|
||||||
Capabilities: ['CAPABILITY_IAM'],
|
|
||||||
};
|
|
||||||
const createStackInput: SDK.CloudFormation.CreateStackInput = {
|
|
||||||
StackName: baseStackName,
|
|
||||||
TemplateBody: baseStack,
|
|
||||||
Parameters: parameters,
|
|
||||||
Capabilities: ['CAPABILITY_IAM'],
|
|
||||||
};
|
|
||||||
|
|
||||||
const stacks = await CF.listStacks({
|
|
||||||
StackStatusFilter: ['UPDATE_COMPLETE', 'CREATE_COMPLETE', 'ROLLBACK_COMPLETE'],
|
|
||||||
}).promise();
|
|
||||||
const stackNames = stacks.StackSummaries?.map((x) => x.StackName) || [];
|
|
||||||
const stackExists: Boolean = stackNames.includes(baseStackName) || false;
|
|
||||||
const describeStack = async () => {
|
|
||||||
return await CF.describeStacks(describeStackInput).promise();
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
if (!stackExists) {
|
|
||||||
CloudRunnerLogger.log(`${baseStackName} stack does not exist (${JSON.stringify(stackNames)})`);
|
|
||||||
await CF.createStack(createStackInput).promise();
|
|
||||||
CloudRunnerLogger.log(`created stack (version: ${parametersHash})`);
|
|
||||||
}
|
|
||||||
const CFState = await describeStack();
|
|
||||||
let stack = CFState.Stacks?.[0];
|
|
||||||
if (!stack) {
|
|
||||||
throw new Error(`Base stack doesn't exist, even after creation, stackExists check: ${stackExists}`);
|
|
||||||
}
|
|
||||||
const stackVersion = stack.Parameters?.find((x) => x.ParameterKey === 'Version')?.ParameterValue;
|
|
||||||
|
|
||||||
if (stack.StackStatus === 'CREATE_IN_PROGRESS') {
|
|
||||||
await CF.waitFor('stackCreateComplete', describeStackInput).promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stackExists) {
|
|
||||||
CloudRunnerLogger.log(`Base stack exists (version: ${stackVersion}, local version: ${parametersHash})`);
|
|
||||||
if (parametersHash !== stackVersion) {
|
|
||||||
CloudRunnerLogger.log(`Attempting update of base stack`);
|
|
||||||
try {
|
|
||||||
await CF.updateStack(updateInput).promise();
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error['message'].includes('No updates are to be performed')) {
|
|
||||||
CloudRunnerLogger.log(`No updates are to be performed`);
|
|
||||||
} else {
|
|
||||||
CloudRunnerLogger.log(`Update Failed (Stack name: ${baseStackName})`);
|
|
||||||
CloudRunnerLogger.log(error['message']);
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(`Continuing...`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CloudRunnerLogger.log(`No update required`);
|
|
||||||
}
|
|
||||||
stack = (await describeStack()).Stacks?.[0];
|
|
||||||
if (!stack) {
|
|
||||||
throw new Error(
|
|
||||||
`Base stack doesn't exist, even after updating and creation, stackExists check: ${stackExists}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (stack.StackStatus === 'UPDATE_IN_PROGRESS') {
|
|
||||||
await CF.waitFor('stackUpdateComplete', describeStackInput).promise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log('base stack is now ready');
|
|
||||||
} catch (error) {
|
|
||||||
core.error(JSON.stringify(await describeStack(), undefined, 4));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import * as SDK from 'aws-sdk';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
|
|
||||||
export class AWSError {
|
|
||||||
static async handleStackCreationFailure(error: any, CF: SDK.CloudFormation, taskDefStackName: string) {
|
|
||||||
CloudRunnerLogger.log('aws error: ');
|
|
||||||
core.error(JSON.stringify(error, undefined, 4));
|
|
||||||
if (CloudRunner.buildParameters.cloudRunnerDebug) {
|
|
||||||
CloudRunnerLogger.log('Getting events and resources for task stack');
|
|
||||||
const events = (await CF.describeStackEvents({ StackName: taskDefStackName }).promise()).StackEvents;
|
|
||||||
CloudRunnerLogger.log(JSON.stringify(events, undefined, 4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
import * as AWS from 'aws-sdk';
|
|
||||||
import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def';
|
|
||||||
import * as zlib from 'node:zlib';
|
|
||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import { Input } from '../../..';
|
|
||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
import { CommandHookService } from '../../services/hooks/command-hook-service';
|
|
||||||
import { FollowLogStreamService } from '../../services/core/follow-log-stream-service';
|
|
||||||
import CloudRunnerOptions from '../../options/cloud-runner-options';
|
|
||||||
import GitHub from '../../../github';
|
|
||||||
|
|
||||||
class AWSTaskRunner {
|
|
||||||
public static ECS: AWS.ECS;
|
|
||||||
public static Kinesis: AWS.Kinesis;
|
|
||||||
private static readonly encodedUnderscore = `$252F`;
|
|
||||||
static async runTask(
|
|
||||||
taskDef: CloudRunnerAWSTaskDef,
|
|
||||||
environment: CloudRunnerEnvironmentVariable[],
|
|
||||||
commands: string,
|
|
||||||
): Promise<{ output: string; shouldCleanup: boolean }> {
|
|
||||||
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 runParameters = {
|
|
||||||
cluster,
|
|
||||||
taskDefinition,
|
|
||||||
platformVersion: '1.4.0',
|
|
||||||
overrides: {
|
|
||||||
containerOverrides: [
|
|
||||||
{
|
|
||||||
name: taskDef.taskDefStackName,
|
|
||||||
environment,
|
|
||||||
command: ['-c', CommandHookService.ApplyHooksToCommands(commands, CloudRunner.buildParameters)],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
launchType: 'FARGATE',
|
|
||||||
networkConfiguration: {
|
|
||||||
awsvpcConfiguration: {
|
|
||||||
subnets: [SubnetOne, SubnetTwo],
|
|
||||||
assignPublicIp: 'ENABLED',
|
|
||||||
securityGroups: [ContainerSecurityGroup],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (JSON.stringify(runParameters.overrides.containerOverrides).length > 8192) {
|
|
||||||
CloudRunnerLogger.log(JSON.stringify(runParameters.overrides.containerOverrides, undefined, 4));
|
|
||||||
throw new Error(`Container Overrides length must be at most 8192`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = await AWSTaskRunner.ECS.runTask(runParameters).promise();
|
|
||||||
const taskArn = task.tasks?.[0].taskArn || '';
|
|
||||||
CloudRunnerLogger.log('Cloud runner job is starting');
|
|
||||||
await AWSTaskRunner.waitUntilTaskRunning(taskArn, cluster);
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Cloud runner job status is running ${(await AWSTaskRunner.describeTasks(cluster, taskArn))?.lastStatus} Async:${
|
|
||||||
CloudRunnerOptions.asyncCloudRunner
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
if (CloudRunnerOptions.asyncCloudRunner) {
|
|
||||||
const shouldCleanup: boolean = false;
|
|
||||||
const output: string = '';
|
|
||||||
CloudRunnerLogger.log(`Watch Cloud Runner To End: false`);
|
|
||||||
|
|
||||||
return { output, shouldCleanup };
|
|
||||||
}
|
|
||||||
|
|
||||||
CloudRunnerLogger.log(`Streaming...`);
|
|
||||||
const { output, shouldCleanup } = await this.streamLogsUntilTaskStops(cluster, taskArn, streamName);
|
|
||||||
let exitCode;
|
|
||||||
let containerState;
|
|
||||||
let taskData;
|
|
||||||
while (exitCode === undefined) {
|
|
||||||
await new Promise((resolve) => resolve(10000));
|
|
||||||
taskData = await AWSTaskRunner.describeTasks(cluster, taskArn);
|
|
||||||
containerState = taskData.containers?.[0];
|
|
||||||
exitCode = containerState?.exitCode;
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(`Container State: ${JSON.stringify(containerState, undefined, 4)}`);
|
|
||||||
if (exitCode === undefined) {
|
|
||||||
CloudRunnerLogger.logWarning(`Undefined exitcode for container`);
|
|
||||||
}
|
|
||||||
const wasSuccessful = exitCode === 0;
|
|
||||||
if (wasSuccessful) {
|
|
||||||
CloudRunnerLogger.log(`Cloud runner job has finished successfully`);
|
|
||||||
|
|
||||||
return { output, shouldCleanup };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (taskData?.stoppedReason === 'Essential container in task exited' && exitCode === 1) {
|
|
||||||
throw new Error('Container exited with code 1');
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Task failed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async waitUntilTaskRunning(taskArn: string, cluster: string) {
|
|
||||||
try {
|
|
||||||
await AWSTaskRunner.ECS.waitFor('tasksRunning', { tasks: [taskArn], cluster }).promise();
|
|
||||||
} catch (error_) {
|
|
||||||
const error = error_ as Error;
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Cloud runner job has ended ${
|
|
||||||
(await AWSTaskRunner.describeTasks(cluster, taskArn)).containers?.[0].lastStatus
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
core.setFailed(error);
|
|
||||||
core.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async describeTasks(clusterName: string, taskArn: string) {
|
|
||||||
const tasks = await AWSTaskRunner.ECS.describeTasks({
|
|
||||||
cluster: clusterName,
|
|
||||||
tasks: [taskArn],
|
|
||||||
}).promise();
|
|
||||||
if (tasks.tasks?.[0]) {
|
|
||||||
return tasks.tasks?.[0];
|
|
||||||
} else {
|
|
||||||
throw new Error('No task found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async streamLogsUntilTaskStops(clusterName: string, taskArn: string, kinesisStreamName: string) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
||||||
CloudRunnerLogger.log(`Streaming...`);
|
|
||||||
const stream = await AWSTaskRunner.getLogStream(kinesisStreamName);
|
|
||||||
let iterator = await AWSTaskRunner.getLogIterator(stream);
|
|
||||||
|
|
||||||
const logBaseUrl = `https://${Input.region}.console.aws.amazon.com/cloudwatch/home?region=${Input.region}#logsV2:log-groups/log-group/${CloudRunner.buildParameters.awsStackName}${AWSTaskRunner.encodedUnderscore}${CloudRunner.buildParameters.awsStackName}-${CloudRunner.buildParameters.buildGuid}`;
|
|
||||||
CloudRunnerLogger.log(`You view the log stream on AWS Cloud Watch: ${logBaseUrl}`);
|
|
||||||
await GitHub.updateGitHubCheck(`You view the log stream on AWS Cloud Watch: ${logBaseUrl}`, ``);
|
|
||||||
let shouldReadLogs = true;
|
|
||||||
let shouldCleanup = true;
|
|
||||||
let timestamp: number = 0;
|
|
||||||
let output = '';
|
|
||||||
while (shouldReadLogs) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
||||||
const taskData = await AWSTaskRunner.describeTasks(clusterName, taskArn);
|
|
||||||
({ timestamp, shouldReadLogs } = AWSTaskRunner.checkStreamingShouldContinue(taskData, timestamp, shouldReadLogs));
|
|
||||||
({ iterator, shouldReadLogs, output, shouldCleanup } = await AWSTaskRunner.handleLogStreamIteration(
|
|
||||||
iterator,
|
|
||||||
shouldReadLogs,
|
|
||||||
output,
|
|
||||||
shouldCleanup,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return { output, shouldCleanup };
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async handleLogStreamIteration(
|
|
||||||
iterator: string,
|
|
||||||
shouldReadLogs: boolean,
|
|
||||||
output: string,
|
|
||||||
shouldCleanup: boolean,
|
|
||||||
) {
|
|
||||||
const records = await AWSTaskRunner.Kinesis.getRecords({
|
|
||||||
ShardIterator: iterator,
|
|
||||||
}).promise();
|
|
||||||
iterator = records.NextShardIterator || '';
|
|
||||||
({ shouldReadLogs, output, shouldCleanup } = AWSTaskRunner.logRecords(
|
|
||||||
records,
|
|
||||||
iterator,
|
|
||||||
shouldReadLogs,
|
|
||||||
output,
|
|
||||||
shouldCleanup,
|
|
||||||
));
|
|
||||||
|
|
||||||
return { iterator, shouldReadLogs, output, shouldCleanup };
|
|
||||||
}
|
|
||||||
|
|
||||||
private static checkStreamingShouldContinue(taskData: AWS.ECS.Task, timestamp: number, shouldReadLogs: boolean) {
|
|
||||||
if (taskData?.lastStatus === 'UNKNOWN') {
|
|
||||||
CloudRunnerLogger.log('## Cloud runner job unknwon');
|
|
||||||
}
|
|
||||||
if (taskData?.lastStatus !== 'RUNNING') {
|
|
||||||
if (timestamp === 0) {
|
|
||||||
CloudRunnerLogger.log('## Cloud runner job stopped, streaming end of logs');
|
|
||||||
timestamp = Date.now();
|
|
||||||
}
|
|
||||||
if (timestamp !== 0 && Date.now() - timestamp > 30000) {
|
|
||||||
CloudRunnerLogger.log('## Cloud runner status is not RUNNING for 30 seconds, last query for logs');
|
|
||||||
shouldReadLogs = false;
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(`## Status of job: ${taskData.lastStatus}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { timestamp, shouldReadLogs };
|
|
||||||
}
|
|
||||||
|
|
||||||
private static logRecords(
|
|
||||||
records: AWS.Kinesis.GetRecordsOutput,
|
|
||||||
iterator: string,
|
|
||||||
shouldReadLogs: boolean,
|
|
||||||
output: string,
|
|
||||||
shouldCleanup: boolean,
|
|
||||||
) {
|
|
||||||
if (records.Records.length > 0 && iterator) {
|
|
||||||
for (const record of records.Records) {
|
|
||||||
const json = JSON.parse(zlib.gunzipSync(Buffer.from(record.Data as string, 'base64')).toString('utf8'));
|
|
||||||
if (json.messageType === 'DATA_MESSAGE') {
|
|
||||||
for (const logEvent of json.logEvents) {
|
|
||||||
({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration(
|
|
||||||
logEvent.message,
|
|
||||||
shouldReadLogs,
|
|
||||||
shouldCleanup,
|
|
||||||
output,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { shouldReadLogs, output, shouldCleanup };
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async getLogStream(kinesisStreamName: string) {
|
|
||||||
return await AWSTaskRunner.Kinesis.describeStream({
|
|
||||||
StreamName: kinesisStreamName,
|
|
||||||
}).promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async getLogIterator(stream: AWS.Kinesis.DescribeStreamOutput) {
|
|
||||||
return (
|
|
||||||
(
|
|
||||||
await AWSTaskRunner.Kinesis.getShardIterator({
|
|
||||||
ShardIteratorType: 'TRIM_HORIZON',
|
|
||||||
StreamName: stream.StreamDescription.StreamName,
|
|
||||||
ShardId: stream.StreamDescription.Shards[0].ShardId,
|
|
||||||
}).promise()
|
|
||||||
).ShardIterator || ''
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default AWSTaskRunner;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import * as AWS from 'aws-sdk';
|
|
||||||
|
|
||||||
class CloudRunnerAWSTaskDef {
|
|
||||||
public taskDefStackName!: string;
|
|
||||||
public taskDefCloudFormation!: string;
|
|
||||||
public taskDefResources: AWS.CloudFormation.StackResources | undefined;
|
|
||||||
public baseResources: AWS.CloudFormation.StackResources | undefined;
|
|
||||||
}
|
|
||||||
export default CloudRunnerAWSTaskDef;
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
import AWS from 'aws-sdk';
|
|
||||||
import Input from '../../../../input';
|
|
||||||
import CloudRunnerLogger from '../../../services/core/cloud-runner-logger';
|
|
||||||
import { BaseStackFormation } from '../cloud-formations/base-stack-formation';
|
|
||||||
import AwsTaskRunner from '../aws-task-runner';
|
|
||||||
import { ListObjectsRequest } from 'aws-sdk/clients/s3';
|
|
||||||
import CloudRunner from '../../../cloud-runner';
|
|
||||||
import { StackSummaries } from 'aws-sdk/clients/cloudformation';
|
|
||||||
import { LogGroups } from 'aws-sdk/clients/cloudwatchlogs';
|
|
||||||
|
|
||||||
export class TaskService {
|
|
||||||
static async watch() {
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const { output, shouldCleanup } = await AwsTaskRunner.streamLogsUntilTaskStops(
|
|
||||||
process.env.cluster || ``,
|
|
||||||
process.env.taskArn || ``,
|
|
||||||
process.env.streamName || ``,
|
|
||||||
);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
public static async getCloudFormationJobStacks() {
|
|
||||||
const result: StackSummaries = [];
|
|
||||||
CloudRunnerLogger.log(``);
|
|
||||||
CloudRunnerLogger.log(`List Cloud Formation Stacks`);
|
|
||||||
process.env.AWS_REGION = Input.region;
|
|
||||||
const CF = new AWS.CloudFormation();
|
|
||||||
const stacks =
|
|
||||||
(await CF.listStacks().promise()).StackSummaries?.filter(
|
|
||||||
(_x) =>
|
|
||||||
_x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription !== BaseStackFormation.baseStackDecription,
|
|
||||||
) || [];
|
|
||||||
CloudRunnerLogger.log(``);
|
|
||||||
CloudRunnerLogger.log(`Cloud Formation Stacks ${stacks.length}`);
|
|
||||||
for (const element of stacks) {
|
|
||||||
const ageDate: Date = new Date(Date.now() - element.CreationTime.getTime());
|
|
||||||
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Task Stack ${element.StackName} - Age D${Math.floor(
|
|
||||||
ageDate.getHours() / 24,
|
|
||||||
)} H${ageDate.getHours()} M${ageDate.getMinutes()}`,
|
|
||||||
);
|
|
||||||
result.push(element);
|
|
||||||
}
|
|
||||||
const baseStacks =
|
|
||||||
(await CF.listStacks().promise()).StackSummaries?.filter(
|
|
||||||
(_x) =>
|
|
||||||
_x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription === BaseStackFormation.baseStackDecription,
|
|
||||||
) || [];
|
|
||||||
CloudRunnerLogger.log(``);
|
|
||||||
CloudRunnerLogger.log(`Base Stacks ${baseStacks.length}`);
|
|
||||||
for (const element of baseStacks) {
|
|
||||||
const ageDate: Date = new Date(Date.now() - element.CreationTime.getTime());
|
|
||||||
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Task Stack ${element.StackName} - Age D${Math.floor(
|
|
||||||
ageDate.getHours() / 24,
|
|
||||||
)} H${ageDate.getHours()} M${ageDate.getMinutes()}`,
|
|
||||||
);
|
|
||||||
result.push(element);
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(``);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
public static async getTasks() {
|
|
||||||
const result: { taskElement: AWS.ECS.Task; element: string }[] = [];
|
|
||||||
CloudRunnerLogger.log(``);
|
|
||||||
CloudRunnerLogger.log(`List Tasks`);
|
|
||||||
process.env.AWS_REGION = Input.region;
|
|
||||||
const ecs = new AWS.ECS();
|
|
||||||
const clusters = (await ecs.listClusters().promise()).clusterArns || [];
|
|
||||||
CloudRunnerLogger.log(`Task 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 };
|
|
||||||
const describeList = (await ecs.describeTasks(describeInput).promise()).tasks || [];
|
|
||||||
if (describeList.length === 0) {
|
|
||||||
CloudRunnerLogger.log(`No Tasks`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(`Tasks ${describeList.length}`);
|
|
||||||
for (const taskElement of describeList) {
|
|
||||||
if (taskElement === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
taskElement.overrides = {};
|
|
||||||
taskElement.attachments = [];
|
|
||||||
if (taskElement.createdAt === undefined) {
|
|
||||||
CloudRunnerLogger.log(`Skipping ${taskElement.taskDefinitionArn} no createdAt date`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.push({ taskElement, element });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(``);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
public static async awsDescribeJob(job: string) {
|
|
||||||
process.env.AWS_REGION = Input.region;
|
|
||||||
const CF = new AWS.CloudFormation();
|
|
||||||
const stack = (await CF.listStacks().promise()).StackSummaries?.find((_x) => _x.StackName === job) || undefined;
|
|
||||||
const stackInfo = (await CF.describeStackResources({ StackName: job }).promise()) || undefined;
|
|
||||||
const stackInfo2 = (await CF.describeStacks({ StackName: job }).promise()) || undefined;
|
|
||||||
if (stack === undefined) {
|
|
||||||
throw new Error('stack not defined');
|
|
||||||
}
|
|
||||||
const ageDate: Date = new Date(Date.now() - stack.CreationTime.getTime());
|
|
||||||
const message = `
|
|
||||||
Task Stack ${stack.StackName}
|
|
||||||
Age D${Math.floor(ageDate.getHours() / 24)} H${ageDate.getHours()} M${ageDate.getMinutes()}
|
|
||||||
${JSON.stringify(stack, undefined, 4)}
|
|
||||||
${JSON.stringify(stackInfo, undefined, 4)}
|
|
||||||
${JSON.stringify(stackInfo2, undefined, 4)}
|
|
||||||
`;
|
|
||||||
CloudRunnerLogger.log(message);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
public static async getLogGroups() {
|
|
||||||
const result: LogGroups = [];
|
|
||||||
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 || []));
|
|
||||||
}
|
|
||||||
|
|
||||||
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: Date = new Date(Date.now() - element.creationTime);
|
|
||||||
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Task Stack ${element.logGroupName} - Age D${Math.floor(
|
|
||||||
ageDate.getHours() / 24,
|
|
||||||
)} H${ageDate.getHours()} M${ageDate.getMinutes()}`,
|
|
||||||
);
|
|
||||||
result.push(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
public static async getLocks() {
|
|
||||||
process.env.AWS_REGION = Input.region;
|
|
||||||
const s3 = new AWS.S3();
|
|
||||||
const listRequest: ListObjectsRequest = {
|
|
||||||
Bucket: CloudRunner.buildParameters.awsStackName,
|
|
||||||
};
|
|
||||||
const results = await s3.listObjects(listRequest).promise();
|
|
||||||
|
|
||||||
return results.Contents || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
import * as k8s from '@kubernetes/client-node';
|
|
||||||
import { BuildParameters } from '../../..';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import { ProviderInterface } from '../provider-interface';
|
|
||||||
import CloudRunnerSecret from '../../options/cloud-runner-secret';
|
|
||||||
import KubernetesStorage from './kubernetes-storage';
|
|
||||||
import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable';
|
|
||||||
import KubernetesTaskRunner from './kubernetes-task-runner';
|
|
||||||
import KubernetesSecret from './kubernetes-secret';
|
|
||||||
import KubernetesJobSpecFactory from './kubernetes-job-spec-factory';
|
|
||||||
import KubernetesServiceAccount from './kubernetes-service-account';
|
|
||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import { CoreV1Api } from '@kubernetes/client-node';
|
|
||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
import { ProviderResource } from '../provider-resource';
|
|
||||||
import { ProviderWorkflow } from '../provider-workflow';
|
|
||||||
|
|
||||||
class Kubernetes implements ProviderInterface {
|
|
||||||
public static Instance: Kubernetes;
|
|
||||||
public kubeConfig!: k8s.KubeConfig;
|
|
||||||
public kubeClient!: k8s.CoreV1Api;
|
|
||||||
public kubeClientBatch!: k8s.BatchV1Api;
|
|
||||||
public buildGuid: string = '';
|
|
||||||
public buildParameters!: BuildParameters;
|
|
||||||
public pvcName: string = '';
|
|
||||||
public secretName: string = '';
|
|
||||||
public jobName: string = '';
|
|
||||||
public namespace!: string;
|
|
||||||
public podName: string = '';
|
|
||||||
public containerName: string = '';
|
|
||||||
public cleanupCronJobName: string = '';
|
|
||||||
public serviceAccountName: string = '';
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
constructor(buildParameters: BuildParameters) {
|
|
||||||
Kubernetes.Instance = this;
|
|
||||||
this.kubeConfig = new k8s.KubeConfig();
|
|
||||||
this.kubeConfig.loadFromDefault();
|
|
||||||
this.kubeClient = this.kubeConfig.makeApiClient(k8s.CoreV1Api);
|
|
||||||
this.kubeClientBatch = this.kubeConfig.makeApiClient(k8s.BatchV1Api);
|
|
||||||
this.namespace = 'default';
|
|
||||||
CloudRunnerLogger.log('Loaded default Kubernetes configuration for this environment');
|
|
||||||
}
|
|
||||||
|
|
||||||
async listResources(): Promise<ProviderResource[]> {
|
|
||||||
const pods = await this.kubeClient.listNamespacedPod(this.namespace);
|
|
||||||
const serviceAccounts = await this.kubeClient.listNamespacedServiceAccount(this.namespace);
|
|
||||||
const secrets = await this.kubeClient.listNamespacedSecret(this.namespace);
|
|
||||||
const jobs = await this.kubeClientBatch.listNamespacedJob(this.namespace);
|
|
||||||
|
|
||||||
return [
|
|
||||||
...pods.body.items.map((x) => {
|
|
||||||
return { Name: x.metadata?.name || `` };
|
|
||||||
}),
|
|
||||||
...serviceAccounts.body.items.map((x) => {
|
|
||||||
return { Name: x.metadata?.name || `` };
|
|
||||||
}),
|
|
||||||
...secrets.body.items.map((x) => {
|
|
||||||
return { Name: x.metadata?.name || `` };
|
|
||||||
}),
|
|
||||||
...jobs.body.items.map((x) => {
|
|
||||||
return { Name: x.metadata?.name || `` };
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
listWorkflow(): Promise<ProviderWorkflow[]> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
watchWorkflow(): Promise<string> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
garbageCollect(
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
filter: string,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
previewOnly: boolean,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
olderThan: Number,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
fullCache: boolean,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
baseDependencies: boolean,
|
|
||||||
): Promise<string> {
|
|
||||||
return new Promise((result) => result(``));
|
|
||||||
}
|
|
||||||
public async setupWorkflow(
|
|
||||||
buildGuid: string,
|
|
||||||
buildParameters: BuildParameters,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
branchName: string,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
this.buildParameters = buildParameters;
|
|
||||||
this.cleanupCronJobName = `unity-builder-cronjob-${buildParameters.buildGuid}`;
|
|
||||||
this.serviceAccountName = `service-account-${buildParameters.buildGuid}`;
|
|
||||||
|
|
||||||
await KubernetesServiceAccount.createServiceAccount(this.serviceAccountName, this.namespace, this.kubeClient);
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async runTaskInWorkflow(
|
|
||||||
buildGuid: string,
|
|
||||||
image: string,
|
|
||||||
commands: string,
|
|
||||||
mountdir: string,
|
|
||||||
workingdir: string,
|
|
||||||
environment: CloudRunnerEnvironmentVariable[],
|
|
||||||
secrets: CloudRunnerSecret[],
|
|
||||||
): Promise<string> {
|
|
||||||
try {
|
|
||||||
CloudRunnerLogger.log('Cloud Runner K8s workflow!');
|
|
||||||
|
|
||||||
// Setup
|
|
||||||
const id = BuildParameters.shouldUseRetainedWorkspaceMode(this.buildParameters)
|
|
||||||
? CloudRunner.lockedWorkspace
|
|
||||||
: this.buildParameters.buildGuid;
|
|
||||||
this.pvcName = `unity-builder-pvc-${id}`;
|
|
||||||
await KubernetesStorage.createPersistentVolumeClaim(
|
|
||||||
this.buildParameters,
|
|
||||||
this.pvcName,
|
|
||||||
this.kubeClient,
|
|
||||||
this.namespace,
|
|
||||||
);
|
|
||||||
this.buildGuid = buildGuid;
|
|
||||||
this.secretName = `build-credentials-${this.buildGuid}`;
|
|
||||||
this.jobName = `unity-builder-job-${this.buildGuid}`;
|
|
||||||
this.containerName = `main`;
|
|
||||||
await KubernetesSecret.createSecret(secrets, this.secretName, this.namespace, this.kubeClient);
|
|
||||||
let output = '';
|
|
||||||
try {
|
|
||||||
CloudRunnerLogger.log('Job does not exist');
|
|
||||||
await this.createJob(commands, image, mountdir, workingdir, environment, secrets);
|
|
||||||
CloudRunnerLogger.log('Watching pod until running');
|
|
||||||
await KubernetesTaskRunner.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
|
|
||||||
|
|
||||||
CloudRunnerLogger.log('Pod running, streaming logs');
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Starting logs follow for pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace} pvc: ${this.pvcName} ${CloudRunner.buildParameters.kubeVolumeSize}/${CloudRunner.buildParameters.containerCpu}/${CloudRunner.buildParameters.containerMemory}`,
|
|
||||||
);
|
|
||||||
output += await KubernetesTaskRunner.runTask(
|
|
||||||
this.kubeConfig,
|
|
||||||
this.kubeClient,
|
|
||||||
this.jobName,
|
|
||||||
this.podName,
|
|
||||||
this.containerName,
|
|
||||||
this.namespace,
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
CloudRunnerLogger.log(`error running k8s workflow ${error}`);
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
JSON.stringify(
|
|
||||||
(await this.kubeClient.listNamespacedEvent(this.namespace)).body.items
|
|
||||||
.map((x) => {
|
|
||||||
return {
|
|
||||||
message: x.message || ``,
|
|
||||||
name: x.metadata.name || ``,
|
|
||||||
reason: x.reason || ``,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((x) => x.name.includes(this.podName)),
|
|
||||||
undefined,
|
|
||||||
4,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await this.cleanupTaskResources();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.cleanupTaskResources();
|
|
||||||
|
|
||||||
return output;
|
|
||||||
} catch (error) {
|
|
||||||
CloudRunnerLogger.log('Running job failed');
|
|
||||||
core.error(JSON.stringify(error, undefined, 4));
|
|
||||||
|
|
||||||
// await this.cleanupTaskResources();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createJob(
|
|
||||||
commands: string,
|
|
||||||
image: string,
|
|
||||||
mountdir: string,
|
|
||||||
workingdir: string,
|
|
||||||
environment: CloudRunnerEnvironmentVariable[],
|
|
||||||
secrets: CloudRunnerSecret[],
|
|
||||||
) {
|
|
||||||
await this.createNamespacedJob(commands, image, mountdir, workingdir, environment, secrets);
|
|
||||||
const find = await Kubernetes.findPodFromJob(this.kubeClient, this.jobName, this.namespace);
|
|
||||||
this.setPodNameAndContainerName(find);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doesJobExist(name: string) {
|
|
||||||
const jobs = await this.kubeClientBatch.listNamespacedJob(this.namespace);
|
|
||||||
|
|
||||||
return jobs.body.items.some((x) => x.metadata?.name === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doesFailedJobExist() {
|
|
||||||
const podStatus = await this.kubeClient.readNamespacedPodStatus(this.podName, this.namespace);
|
|
||||||
|
|
||||||
return podStatus.body.status?.phase === `Failed`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createNamespacedJob(
|
|
||||||
commands: string,
|
|
||||||
image: string,
|
|
||||||
mountdir: string,
|
|
||||||
workingdir: string,
|
|
||||||
environment: CloudRunnerEnvironmentVariable[],
|
|
||||||
secrets: CloudRunnerSecret[],
|
|
||||||
) {
|
|
||||||
for (let index = 0; index < 3; index++) {
|
|
||||||
try {
|
|
||||||
const jobSpec = KubernetesJobSpecFactory.getJobSpec(
|
|
||||||
commands,
|
|
||||||
image,
|
|
||||||
mountdir,
|
|
||||||
workingdir,
|
|
||||||
environment,
|
|
||||||
secrets,
|
|
||||||
this.buildGuid,
|
|
||||||
this.buildParameters,
|
|
||||||
this.secretName,
|
|
||||||
this.pvcName,
|
|
||||||
this.jobName,
|
|
||||||
k8s,
|
|
||||||
this.containerName,
|
|
||||||
);
|
|
||||||
await new Promise((promise) => setTimeout(promise, 15000));
|
|
||||||
const result = await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
|
|
||||||
CloudRunnerLogger.log(`Build job created`);
|
|
||||||
await new Promise((promise) => setTimeout(promise, 5000));
|
|
||||||
CloudRunnerLogger.log('Job created');
|
|
||||||
|
|
||||||
return result.body.metadata?.name;
|
|
||||||
} catch (error) {
|
|
||||||
CloudRunnerLogger.log(`Error occured creating job: ${error}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPodNameAndContainerName(pod: k8s.V1Pod) {
|
|
||||||
this.podName = pod.metadata?.name || '';
|
|
||||||
this.containerName = pod.status?.containerStatuses?.[0].name || this.containerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
async cleanupTaskResources() {
|
|
||||||
CloudRunnerLogger.log('cleaning up');
|
|
||||||
try {
|
|
||||||
await this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
|
|
||||||
await this.kubeClient.deleteNamespacedPod(this.podName, this.namespace);
|
|
||||||
} catch (error: any) {
|
|
||||||
CloudRunnerLogger.log(`Failed to cleanup`);
|
|
||||||
if (error.response.body.reason !== `NotFound`) {
|
|
||||||
CloudRunnerLogger.log(`Wasn't a not found error: ${error.response.body.reason}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.kubeClient.deleteNamespacedSecret(this.secretName, this.namespace);
|
|
||||||
} catch (error: any) {
|
|
||||||
CloudRunnerLogger.log(`Failed to cleanup secret`);
|
|
||||||
CloudRunnerLogger.log(error.response.body.reason);
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log('cleaned up Secret, Job and Pod');
|
|
||||||
CloudRunnerLogger.log('cleaning up finished');
|
|
||||||
}
|
|
||||||
|
|
||||||
async cleanupWorkflow(
|
|
||||||
buildGuid: string,
|
|
||||||
buildParameters: BuildParameters,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
branchName: string,
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
|
|
||||||
) {
|
|
||||||
if (BuildParameters.shouldUseRetainedWorkspaceMode(buildParameters)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(`deleting PVC`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.kubeClient.deleteNamespacedPersistentVolumeClaim(this.pvcName, this.namespace);
|
|
||||||
await this.kubeClient.deleteNamespacedServiceAccount(this.serviceAccountName, this.namespace);
|
|
||||||
CloudRunnerLogger.log('cleaned up PVC and Service Account');
|
|
||||||
} catch (error: any) {
|
|
||||||
CloudRunnerLogger.log(`Cleanup failed ${JSON.stringify(error, undefined, 4)}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async findPodFromJob(kubeClient: CoreV1Api, jobName: string, namespace: string) {
|
|
||||||
const namespacedPods = await kubeClient.listNamespacedPod(namespace);
|
|
||||||
const pod = namespacedPods.body.items.find((x) => x.metadata?.labels?.['job-name'] === jobName);
|
|
||||||
if (pod === undefined) {
|
|
||||||
throw new Error("pod with job-name label doesn't exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
return pod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default Kubernetes;
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { V1EnvVar, V1EnvVarSource, V1SecretKeySelector } from '@kubernetes/client-node';
|
|
||||||
import BuildParameters from '../../../build-parameters';
|
|
||||||
import { CommandHookService } from '../../services/hooks/command-hook-service';
|
|
||||||
import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable';
|
|
||||||
import CloudRunnerSecret from '../../options/cloud-runner-secret';
|
|
||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
|
|
||||||
class KubernetesJobSpecFactory {
|
|
||||||
static getJobSpec(
|
|
||||||
command: string,
|
|
||||||
image: string,
|
|
||||||
mountdir: string,
|
|
||||||
workingDirectory: string,
|
|
||||||
environment: CloudRunnerEnvironmentVariable[],
|
|
||||||
secrets: CloudRunnerSecret[],
|
|
||||||
buildGuid: string,
|
|
||||||
buildParameters: BuildParameters,
|
|
||||||
secretName: string,
|
|
||||||
pvcName: string,
|
|
||||||
jobName: string,
|
|
||||||
k8s: any,
|
|
||||||
containerName: string,
|
|
||||||
) {
|
|
||||||
const job = new k8s.V1Job();
|
|
||||||
job.apiVersion = 'batch/v1';
|
|
||||||
job.kind = 'Job';
|
|
||||||
job.metadata = {
|
|
||||||
name: jobName,
|
|
||||||
labels: {
|
|
||||||
app: 'unity-builder',
|
|
||||||
buildGuid,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
job.spec = {
|
|
||||||
ttlSecondsAfterFinished: 9999,
|
|
||||||
backoffLimit: 0,
|
|
||||||
template: {
|
|
||||||
spec: {
|
|
||||||
volumes: [
|
|
||||||
{
|
|
||||||
name: 'build-mount',
|
|
||||||
persistentVolumeClaim: {
|
|
||||||
claimName: pvcName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
containers: [
|
|
||||||
{
|
|
||||||
ttlSecondsAfterFinished: 9999,
|
|
||||||
name: containerName,
|
|
||||||
image,
|
|
||||||
command: ['/bin/sh'],
|
|
||||||
args: [
|
|
||||||
'-c',
|
|
||||||
`${CommandHookService.ApplyHooksToCommands(`${command}\nsleep 2m`, CloudRunner.buildParameters)}`,
|
|
||||||
],
|
|
||||||
|
|
||||||
workingDir: `${workingDirectory}`,
|
|
||||||
resources: {
|
|
||||||
requests: {
|
|
||||||
memory: `${Number.parseInt(buildParameters.containerMemory) / 1024}G` || '750M',
|
|
||||||
cpu: Number.parseInt(buildParameters.containerCpu) / 1024 || '1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
env: [
|
|
||||||
...environment.map((x) => {
|
|
||||||
const environmentVariable = new V1EnvVar();
|
|
||||||
environmentVariable.name = x.name;
|
|
||||||
environmentVariable.value = x.value;
|
|
||||||
|
|
||||||
return environmentVariable;
|
|
||||||
}),
|
|
||||||
...secrets.map((x) => {
|
|
||||||
const secret = new V1EnvVarSource();
|
|
||||||
secret.secretKeyRef = new V1SecretKeySelector();
|
|
||||||
secret.secretKeyRef.key = x.ParameterKey;
|
|
||||||
secret.secretKeyRef.name = secretName;
|
|
||||||
const environmentVariable = new V1EnvVar();
|
|
||||||
environmentVariable.name = x.EnvironmentVariable;
|
|
||||||
environmentVariable.valueFrom = secret;
|
|
||||||
|
|
||||||
return environmentVariable;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
volumeMounts: [
|
|
||||||
{
|
|
||||||
name: 'build-mount',
|
|
||||||
mountPath: `${mountdir}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
lifecycle: {
|
|
||||||
preStop: {
|
|
||||||
exec: {
|
|
||||||
command: [
|
|
||||||
'bin/bash',
|
|
||||||
'-c',
|
|
||||||
`cd /data/builder/action/steps;
|
|
||||||
chmod +x /return_license.sh;
|
|
||||||
/return_license.sh;`,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
restartPolicy: 'Never',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
job.spec.template.spec.containers[0].resources.requests[`ephemeral-storage`] = '10Gi';
|
|
||||||
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default KubernetesJobSpecFactory;
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import { CoreV1Api } from '@kubernetes/client-node';
|
|
||||||
class KubernetesPods {
|
|
||||||
public static async IsPodRunning(podName: string, namespace: string, kubeClient: CoreV1Api) {
|
|
||||||
const pods = (await kubeClient.listNamespacedPod(namespace)).body.items.filter((x) => podName === x.metadata?.name);
|
|
||||||
const running = pods.length > 0 && (pods[0].status?.phase === `Running` || pods[0].status?.phase === `Pending`);
|
|
||||||
const phase = pods[0]?.status?.phase || 'undefined status';
|
|
||||||
CloudRunnerLogger.log(`Getting pod status: ${phase}`);
|
|
||||||
if (phase === `Failed`) {
|
|
||||||
throw new Error(`K8s pod failed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
public static async GetPodStatus(podName: string, namespace: string, kubeClient: CoreV1Api) {
|
|
||||||
const pods = (await kubeClient.listNamespacedPod(namespace)).body.items.find((x) => podName === x.metadata?.name);
|
|
||||||
const phase = pods?.status?.phase || 'undefined status';
|
|
||||||
|
|
||||||
return phase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default KubernetesPods;
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { waitUntil } from 'async-wait-until';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import * as k8s from '@kubernetes/client-node';
|
|
||||||
import BuildParameters from '../../../build-parameters';
|
|
||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import { IncomingMessage } from 'node:http';
|
|
||||||
import GitHub from '../../../github';
|
|
||||||
|
|
||||||
class KubernetesStorage {
|
|
||||||
public static async createPersistentVolumeClaim(
|
|
||||||
buildParameters: BuildParameters,
|
|
||||||
pvcName: string,
|
|
||||||
kubeClient: k8s.CoreV1Api,
|
|
||||||
namespace: string,
|
|
||||||
) {
|
|
||||||
if (buildParameters.kubeVolume !== ``) {
|
|
||||||
CloudRunnerLogger.log(`Kube Volume was input was set ${buildParameters.kubeVolume} overriding ${pvcName}`);
|
|
||||||
pvcName = buildParameters.kubeVolume;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const allPvc = (await kubeClient.listNamespacedPersistentVolumeClaim(namespace)).body.items;
|
|
||||||
const pvcList = allPvc.map((x) => x.metadata?.name);
|
|
||||||
CloudRunnerLogger.log(`Current PVCs in namespace ${namespace}`);
|
|
||||||
CloudRunnerLogger.log(JSON.stringify(pvcList, undefined, 4));
|
|
||||||
if (pvcList.includes(pvcName)) {
|
|
||||||
CloudRunnerLogger.log(`pvc ${pvcName} already exists`);
|
|
||||||
if (GitHub.githubInputEnabled) {
|
|
||||||
core.setOutput('volume', pvcName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(`Creating PVC ${pvcName} (does not exist)`);
|
|
||||||
const result = await KubernetesStorage.createPVC(pvcName, buildParameters, kubeClient, namespace);
|
|
||||||
await KubernetesStorage.handleResult(result, kubeClient, namespace, pvcName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getPVCPhase(kubeClient: k8s.CoreV1Api, name: string, namespace: string) {
|
|
||||||
try {
|
|
||||||
return (await kubeClient.readNamespacedPersistentVolumeClaim(name, namespace)).body.status?.phase;
|
|
||||||
} catch (error) {
|
|
||||||
core.error('Failed to get PVC phase');
|
|
||||||
core.error(JSON.stringify(error, undefined, 4));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async watchUntilPVCNotPending(kubeClient: k8s.CoreV1Api, name: string, namespace: string) {
|
|
||||||
try {
|
|
||||||
CloudRunnerLogger.log(`watch Until PVC Not Pending ${name} ${namespace}`);
|
|
||||||
CloudRunnerLogger.log(`${await this.getPVCPhase(kubeClient, name, namespace)}`);
|
|
||||||
await waitUntil(
|
|
||||||
async () => {
|
|
||||||
return (await this.getPVCPhase(kubeClient, name, namespace)) === 'Pending';
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeout: 750000,
|
|
||||||
intervalBetweenAttempts: 15000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
core.error('Failed to watch PVC');
|
|
||||||
core.error(error.toString());
|
|
||||||
core.error(
|
|
||||||
`PVC Body: ${JSON.stringify(
|
|
||||||
(await kubeClient.readNamespacedPersistentVolumeClaim(name, namespace)).body,
|
|
||||||
undefined,
|
|
||||||
4,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async createPVC(
|
|
||||||
pvcName: string,
|
|
||||||
buildParameters: BuildParameters,
|
|
||||||
kubeClient: k8s.CoreV1Api,
|
|
||||||
namespace: string,
|
|
||||||
) {
|
|
||||||
const pvc = new k8s.V1PersistentVolumeClaim();
|
|
||||||
pvc.apiVersion = 'v1';
|
|
||||||
pvc.kind = 'PersistentVolumeClaim';
|
|
||||||
pvc.metadata = {
|
|
||||||
name: pvcName,
|
|
||||||
};
|
|
||||||
pvc.spec = {
|
|
||||||
accessModes: ['ReadWriteOnce'],
|
|
||||||
storageClassName: buildParameters.kubeStorageClass === '' ? 'standard' : buildParameters.kubeStorageClass,
|
|
||||||
resources: {
|
|
||||||
requests: {
|
|
||||||
storage: buildParameters.kubeVolumeSize,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = await kubeClient.createNamespacedPersistentVolumeClaim(namespace, pvc);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async handleResult(
|
|
||||||
result: { response: IncomingMessage; body: k8s.V1PersistentVolumeClaim },
|
|
||||||
kubeClient: k8s.CoreV1Api,
|
|
||||||
namespace: string,
|
|
||||||
pvcName: string,
|
|
||||||
) {
|
|
||||||
const name = result.body.metadata?.name || '';
|
|
||||||
CloudRunnerLogger.log(`PVC ${name} created`);
|
|
||||||
await this.watchUntilPVCNotPending(kubeClient, name, namespace);
|
|
||||||
CloudRunnerLogger.log(`PVC ${name} is ready and not pending`);
|
|
||||||
core.setOutput('volume', pvcName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default KubernetesStorage;
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
import { CoreV1Api, KubeConfig } from '@kubernetes/client-node';
|
|
||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import { waitUntil } from 'async-wait-until';
|
|
||||||
import { CloudRunnerSystem } from '../../services/core/cloud-runner-system';
|
|
||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
import KubernetesPods from './kubernetes-pods';
|
|
||||||
import { FollowLogStreamService } from '../../services/core/follow-log-stream-service';
|
|
||||||
|
|
||||||
class KubernetesTaskRunner {
|
|
||||||
static lastReceivedTimestamp: number = 0;
|
|
||||||
static readonly maxRetry: number = 3;
|
|
||||||
static lastReceivedMessage: string = ``;
|
|
||||||
|
|
||||||
static async runTask(
|
|
||||||
kubeConfig: KubeConfig,
|
|
||||||
kubeClient: CoreV1Api,
|
|
||||||
jobName: string,
|
|
||||||
podName: string,
|
|
||||||
containerName: string,
|
|
||||||
namespace: string,
|
|
||||||
) {
|
|
||||||
let output = '';
|
|
||||||
let shouldReadLogs = true;
|
|
||||||
let shouldCleanup = true;
|
|
||||||
let sinceTime = ``;
|
|
||||||
let retriesAfterFinish = 0;
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
||||||
const lastReceivedMessage =
|
|
||||||
KubernetesTaskRunner.lastReceivedTimestamp > 0
|
|
||||||
? `\nLast Log Message "${this.lastReceivedMessage}" ${this.lastReceivedTimestamp}`
|
|
||||||
: ``;
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Streaming logs from pod: ${podName} container: ${containerName} namespace: ${namespace} ${CloudRunner.buildParameters.kubeVolumeSize}/${CloudRunner.buildParameters.containerCpu}/${CloudRunner.buildParameters.containerMemory}\n${lastReceivedMessage}`,
|
|
||||||
);
|
|
||||||
if (KubernetesTaskRunner.lastReceivedTimestamp > 0) {
|
|
||||||
const currentDate = new Date(KubernetesTaskRunner.lastReceivedTimestamp);
|
|
||||||
const dateTimeIsoString = currentDate.toISOString();
|
|
||||||
sinceTime = ` --since-time="${dateTimeIsoString}"`;
|
|
||||||
}
|
|
||||||
let extraFlags = ``;
|
|
||||||
extraFlags += (await KubernetesPods.IsPodRunning(podName, namespace, kubeClient))
|
|
||||||
? ` -f -c ${containerName}`
|
|
||||||
: ` --previous`;
|
|
||||||
let lastMessageSeenIncludedInChunk = false;
|
|
||||||
let lastMessageSeen = false;
|
|
||||||
|
|
||||||
let logs;
|
|
||||||
|
|
||||||
try {
|
|
||||||
logs = await CloudRunnerSystem.Run(
|
|
||||||
`kubectl logs ${podName}${extraFlags} --timestamps${sinceTime}`,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
||||||
const continueStreaming = await KubernetesPods.IsPodRunning(podName, namespace, kubeClient);
|
|
||||||
CloudRunnerLogger.log(`K8s logging error ${error} ${continueStreaming}`);
|
|
||||||
if (continueStreaming) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (retriesAfterFinish < KubernetesTaskRunner.maxRetry) {
|
|
||||||
retriesAfterFinish++;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
const splitLogs = logs.split(`\n`);
|
|
||||||
for (const chunk of splitLogs) {
|
|
||||||
if (
|
|
||||||
chunk.replace(/\s/g, ``) === KubernetesTaskRunner.lastReceivedMessage.replace(/\s/g, ``) &&
|
|
||||||
KubernetesTaskRunner.lastReceivedMessage.replace(/\s/g, ``) !== ``
|
|
||||||
) {
|
|
||||||
CloudRunnerLogger.log(`Previous log message found ${chunk}`);
|
|
||||||
lastMessageSeenIncludedInChunk = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const chunk of splitLogs) {
|
|
||||||
const newDate = Date.parse(`${chunk.toString().split(`Z `)[0]}Z`);
|
|
||||||
if (chunk.replace(/\s/g, ``) === KubernetesTaskRunner.lastReceivedMessage.replace(/\s/g, ``)) {
|
|
||||||
lastMessageSeen = true;
|
|
||||||
}
|
|
||||||
if (lastMessageSeenIncludedInChunk && !lastMessageSeen) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const message = CloudRunner.buildParameters.cloudRunnerDebug ? chunk : chunk.split(`Z `)[1];
|
|
||||||
KubernetesTaskRunner.lastReceivedMessage = chunk;
|
|
||||||
KubernetesTaskRunner.lastReceivedTimestamp = newDate;
|
|
||||||
({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration(
|
|
||||||
message,
|
|
||||||
shouldReadLogs,
|
|
||||||
shouldCleanup,
|
|
||||||
output,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (FollowLogStreamService.DidReceiveEndOfTransmission) {
|
|
||||||
CloudRunnerLogger.log('end of log stream');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async watchUntilPodRunning(kubeClient: CoreV1Api, podName: string, namespace: string) {
|
|
||||||
let success: boolean = false;
|
|
||||||
let message = ``;
|
|
||||||
CloudRunnerLogger.log(`Watching ${podName} ${namespace}`);
|
|
||||||
await waitUntil(
|
|
||||||
async () => {
|
|
||||||
const status = await kubeClient.readNamespacedPodStatus(podName, namespace);
|
|
||||||
const phase = status?.body.status?.phase;
|
|
||||||
success = phase === 'Running';
|
|
||||||
message = `Phase:${status.body.status?.phase} \n Reason:${
|
|
||||||
status.body.status?.conditions?.[0].reason || ''
|
|
||||||
} \n Message:${status.body.status?.conditions?.[0].message || ''}`;
|
|
||||||
|
|
||||||
// CloudRunnerLogger.log(
|
|
||||||
// JSON.stringify(
|
|
||||||
// (await kubeClient.listNamespacedEvent(namespace)).body.items
|
|
||||||
// .map((x) => {
|
|
||||||
// return {
|
|
||||||
// message: x.message || ``,
|
|
||||||
// name: x.metadata.name || ``,
|
|
||||||
// reason: x.reason || ``,
|
|
||||||
// };
|
|
||||||
// })
|
|
||||||
// .filter((x) => x.name.includes(podName)),
|
|
||||||
// undefined,
|
|
||||||
// 4,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
if (success || phase !== 'Pending') return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeout: 2000000,
|
|
||||||
intervalBetweenAttempts: 15000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!success) {
|
|
||||||
CloudRunnerLogger.log(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default KubernetesTaskRunner;
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
import { assert } from 'node:console';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
import { CloudRunnerFolders } from '../options/cloud-runner-folders';
|
|
||||||
import { CloudRunnerSystem } from '../services/core/cloud-runner-system';
|
|
||||||
import { LfsHashing } from '../services/utility/lfs-hashing';
|
|
||||||
import { RemoteClientLogger } from './remote-client-logger';
|
|
||||||
import { Cli } from '../../cli/cli';
|
|
||||||
import { CliFunction } from '../../cli/cli-functions-repository';
|
|
||||||
// eslint-disable-next-line github/no-then
|
|
||||||
const fileExists = async (fpath: fs.PathLike) => !!(await fs.promises.stat(fpath).catch(() => false));
|
|
||||||
|
|
||||||
export class Caching {
|
|
||||||
@CliFunction(`cache-push`, `push to cache`)
|
|
||||||
static async cachePush() {
|
|
||||||
try {
|
|
||||||
const buildParameter = JSON.parse(process.env.BUILD_PARAMETERS || '{}');
|
|
||||||
CloudRunner.buildParameters = buildParameter;
|
|
||||||
await Caching.PushToCache(
|
|
||||||
Cli.options!['cachePushTo'],
|
|
||||||
Cli.options!['cachePushFrom'],
|
|
||||||
Cli.options!['artifactName'] || '',
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
CloudRunnerLogger.log(`${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@CliFunction(`cache-pull`, `pull from cache`)
|
|
||||||
static async cachePull() {
|
|
||||||
try {
|
|
||||||
const buildParameter = JSON.parse(process.env.BUILD_PARAMETERS || '{}');
|
|
||||||
CloudRunner.buildParameters = buildParameter;
|
|
||||||
await Caching.PullFromCache(
|
|
||||||
Cli.options!['cachePushFrom'],
|
|
||||||
Cli.options!['cachePushTo'],
|
|
||||||
Cli.options!['artifactName'] || '',
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
CloudRunnerLogger.log(`${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async PushToCache(cacheFolder: string, sourceFolder: string, cacheArtifactName: string) {
|
|
||||||
CloudRunnerLogger.log(`Pushing to cache ${sourceFolder}`);
|
|
||||||
cacheArtifactName = cacheArtifactName.replace(' ', '');
|
|
||||||
const startPath = process.cwd();
|
|
||||||
let compressionSuffix = '';
|
|
||||||
if (CloudRunner.buildParameters.useCompressionStrategy === true) {
|
|
||||||
compressionSuffix = `.lz4`;
|
|
||||||
}
|
|
||||||
CloudRunnerLogger.log(`Compression: ${CloudRunner.buildParameters.useCompressionStrategy} ${compressionSuffix}`);
|
|
||||||
try {
|
|
||||||
if (!(await fileExists(cacheFolder))) {
|
|
||||||
await CloudRunnerSystem.Run(`mkdir -p ${cacheFolder}`);
|
|
||||||
}
|
|
||||||
process.chdir(path.resolve(sourceFolder, '..'));
|
|
||||||
|
|
||||||
if (CloudRunner.buildParameters.cloudRunnerDebug === true) {
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Hashed cache folder ${await LfsHashing.hashAllFiles(sourceFolder)} ${sourceFolder} ${path.basename(
|
|
||||||
sourceFolder,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const contents = await fs.promises.readdir(path.basename(sourceFolder));
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`There is ${contents.length} files/dir in the source folder ${path.basename(sourceFolder)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contents.length === 0) {
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Did not push source folder to cache because it was empty ${path.basename(sourceFolder)}`,
|
|
||||||
);
|
|
||||||
process.chdir(`${startPath}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await CloudRunnerSystem.Run(
|
|
||||||
`tar -cf ${cacheArtifactName}.tar${compressionSuffix} ${path.basename(sourceFolder)}`,
|
|
||||||
);
|
|
||||||
await CloudRunnerSystem.Run(`du ${cacheArtifactName}.tar${compressionSuffix}`);
|
|
||||||
assert(await fileExists(`${cacheArtifactName}.tar${compressionSuffix}`), 'cache archive exists');
|
|
||||||
assert(await fileExists(path.basename(sourceFolder)), 'source folder exists');
|
|
||||||
await CloudRunnerSystem.Run(`mv ${cacheArtifactName}.tar${compressionSuffix} ${cacheFolder}`);
|
|
||||||
RemoteClientLogger.log(`moved cache entry ${cacheArtifactName} to ${cacheFolder}`);
|
|
||||||
assert(
|
|
||||||
await fileExists(`${path.join(cacheFolder, cacheArtifactName)}.tar${compressionSuffix}`),
|
|
||||||
'cache archive exists inside cache folder',
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
process.chdir(`${startPath}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
process.chdir(`${startPath}`);
|
|
||||||
}
|
|
||||||
public static async PullFromCache(cacheFolder: string, destinationFolder: string, cacheArtifactName: string = ``) {
|
|
||||||
CloudRunnerLogger.log(`Pulling from cache ${destinationFolder} ${CloudRunner.buildParameters.skipCache}`);
|
|
||||||
if (`${CloudRunner.buildParameters.skipCache}` === `true`) {
|
|
||||||
CloudRunnerLogger.log(`Skipping cache debugSkipCache is true`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cacheArtifactName = cacheArtifactName.replace(' ', '');
|
|
||||||
let compressionSuffix = '';
|
|
||||||
if (CloudRunner.buildParameters.useCompressionStrategy === true) {
|
|
||||||
compressionSuffix = `.lz4`;
|
|
||||||
}
|
|
||||||
const startPath = process.cwd();
|
|
||||||
RemoteClientLogger.log(`Caching for (lz4 ${compressionSuffix}) ${path.basename(destinationFolder)}`);
|
|
||||||
try {
|
|
||||||
if (!(await fileExists(cacheFolder))) {
|
|
||||||
await fs.promises.mkdir(cacheFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await fileExists(destinationFolder))) {
|
|
||||||
await fs.promises.mkdir(destinationFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestInBranch = await (
|
|
||||||
await CloudRunnerSystem.Run(`ls -t "${cacheFolder}" | grep .tar${compressionSuffix}$ | head -1`)
|
|
||||||
)
|
|
||||||
.replace(/\n/g, ``)
|
|
||||||
.replace(`.tar${compressionSuffix}`, '');
|
|
||||||
|
|
||||||
process.chdir(cacheFolder);
|
|
||||||
|
|
||||||
const cacheSelection =
|
|
||||||
cacheArtifactName !== `` && (await fileExists(`${cacheArtifactName}.tar${compressionSuffix}`))
|
|
||||||
? cacheArtifactName
|
|
||||||
: latestInBranch;
|
|
||||||
await CloudRunnerLogger.log(`cache key ${cacheArtifactName} selection ${cacheSelection}`);
|
|
||||||
|
|
||||||
if (await fileExists(`${cacheSelection}.tar${compressionSuffix}`)) {
|
|
||||||
const resultsFolder = `results${CloudRunner.buildParameters.buildGuid}`;
|
|
||||||
await CloudRunnerSystem.Run(`mkdir -p ${resultsFolder}`);
|
|
||||||
RemoteClientLogger.log(`cache item exists ${cacheFolder}/${cacheSelection}.tar${compressionSuffix}`);
|
|
||||||
const fullResultsFolder = path.join(cacheFolder, resultsFolder);
|
|
||||||
await CloudRunnerSystem.Run(`tar -xf ${cacheSelection}.tar${compressionSuffix} -C ${fullResultsFolder}`);
|
|
||||||
RemoteClientLogger.log(`cache item extracted to ${fullResultsFolder}`);
|
|
||||||
assert(await fileExists(fullResultsFolder), `cache extraction results folder exists`);
|
|
||||||
const destinationParentFolder = path.resolve(destinationFolder, '..');
|
|
||||||
|
|
||||||
if (await fileExists(destinationFolder)) {
|
|
||||||
await fs.promises.rmdir(destinationFolder, { recursive: true });
|
|
||||||
}
|
|
||||||
await CloudRunnerSystem.Run(
|
|
||||||
`mv "${path.join(fullResultsFolder, path.basename(destinationFolder))}" "${destinationParentFolder}"`,
|
|
||||||
);
|
|
||||||
const contents = await fs.promises.readdir(
|
|
||||||
path.join(destinationParentFolder, path.basename(destinationFolder)),
|
|
||||||
);
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`There is ${contents.length} files/dir in the cache pulled contents for ${path.basename(destinationFolder)}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
RemoteClientLogger.logWarning(`cache item ${cacheArtifactName} doesn't exist ${destinationFolder}`);
|
|
||||||
if (cacheSelection !== ``) {
|
|
||||||
RemoteClientLogger.logWarning(
|
|
||||||
`cache item ${cacheArtifactName}.tar${compressionSuffix} doesn't exist ${destinationFolder}`,
|
|
||||||
);
|
|
||||||
throw new Error(`Failed to get cache item, but cache hit was found: ${cacheSelection}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
process.chdir(startPath);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
process.chdir(startPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async handleCachePurging() {
|
|
||||||
if (process.env.PURGE_REMOTE_BUILDER_CACHE !== undefined) {
|
|
||||||
RemoteClientLogger.log(`purging ${CloudRunnerFolders.purgeRemoteCaching}`);
|
|
||||||
fs.promises.rmdir(CloudRunnerFolders.cacheFolder, { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
import { CloudRunnerFolders } from '../options/cloud-runner-folders';
|
|
||||||
import { Caching } from './caching';
|
|
||||||
import { LfsHashing } from '../services/utility/lfs-hashing';
|
|
||||||
import { RemoteClientLogger } from './remote-client-logger';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { assert } from 'node:console';
|
|
||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
import { CliFunction } from '../../cli/cli-functions-repository';
|
|
||||||
import { CloudRunnerSystem } from '../services/core/cloud-runner-system';
|
|
||||||
import YAML from 'yaml';
|
|
||||||
import GitHub from '../../github';
|
|
||||||
import BuildParameters from '../../build-parameters';
|
|
||||||
|
|
||||||
export class RemoteClient {
|
|
||||||
@CliFunction(`remote-cli-pre-build`, `sets up a repository, usually before a game-ci build`)
|
|
||||||
static async runRemoteClientJob() {
|
|
||||||
CloudRunnerLogger.log(`bootstrap game ci cloud runner...`);
|
|
||||||
if (!(await RemoteClient.handleRetainedWorkspace())) {
|
|
||||||
await RemoteClient.bootstrapRepository();
|
|
||||||
}
|
|
||||||
await RemoteClient.runCustomHookFiles(`before-build`);
|
|
||||||
}
|
|
||||||
static async runCustomHookFiles(hookLifecycle: string) {
|
|
||||||
RemoteClientLogger.log(`RunCustomHookFiles: ${hookLifecycle}`);
|
|
||||||
const gameCiCustomHooksPath = path.join(CloudRunnerFolders.repoPathAbsolute, `game-ci`, `hooks`);
|
|
||||||
try {
|
|
||||||
const files = fs.readdirSync(gameCiCustomHooksPath);
|
|
||||||
for (const file of files) {
|
|
||||||
const fileContents = fs.readFileSync(path.join(gameCiCustomHooksPath, file), `utf8`);
|
|
||||||
const fileContentsObject = YAML.parse(fileContents.toString());
|
|
||||||
if (fileContentsObject.hook === hookLifecycle) {
|
|
||||||
RemoteClientLogger.log(`Active Hook File ${file} \n \n file contents: \n ${fileContents}`);
|
|
||||||
await CloudRunnerSystem.Run(fileContentsObject.commands);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
RemoteClientLogger.log(JSON.stringify(error, undefined, 4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static async bootstrapRepository() {
|
|
||||||
await CloudRunnerSystem.Run(
|
|
||||||
`mkdir -p ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute)}`,
|
|
||||||
);
|
|
||||||
await CloudRunnerSystem.Run(
|
|
||||||
`mkdir -p ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.cacheFolderForCacheKeyFull)}`,
|
|
||||||
);
|
|
||||||
await RemoteClient.cloneRepoWithoutLFSFiles();
|
|
||||||
await RemoteClient.replaceLargePackageReferencesWithSharedReferences();
|
|
||||||
await RemoteClient.sizeOfFolder(
|
|
||||||
'repo before lfs cache pull',
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute),
|
|
||||||
);
|
|
||||||
const lfsHashes = await LfsHashing.createLFSHashFiles();
|
|
||||||
if (fs.existsSync(CloudRunnerFolders.libraryFolderAbsolute)) {
|
|
||||||
RemoteClientLogger.logWarning(`!Warning!: The Unity library was included in the git repository`);
|
|
||||||
}
|
|
||||||
await Caching.PullFromCache(
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.lfsCacheFolderFull),
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.lfsFolderAbsolute),
|
|
||||||
`${lfsHashes.lfsGuidSum}`,
|
|
||||||
);
|
|
||||||
await RemoteClient.sizeOfFolder('repo after lfs cache pull', CloudRunnerFolders.repoPathAbsolute);
|
|
||||||
await RemoteClient.pullLatestLFS();
|
|
||||||
await RemoteClient.sizeOfFolder('repo before lfs git pull', CloudRunnerFolders.repoPathAbsolute);
|
|
||||||
await Caching.PushToCache(
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.lfsCacheFolderFull),
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.lfsFolderAbsolute),
|
|
||||||
`${lfsHashes.lfsGuidSum}`,
|
|
||||||
);
|
|
||||||
await Caching.PullFromCache(
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.libraryCacheFolderFull),
|
|
||||||
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.libraryFolderAbsolute),
|
|
||||||
);
|
|
||||||
await RemoteClient.sizeOfFolder('repo after library cache pull', CloudRunnerFolders.repoPathAbsolute);
|
|
||||||
await Caching.handleCachePurging();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async sizeOfFolder(message: string, folder: string) {
|
|
||||||
if (CloudRunner.buildParameters.cloudRunnerDebug) {
|
|
||||||
CloudRunnerLogger.log(`Size of ${message}`);
|
|
||||||
await CloudRunnerSystem.Run(`du -sh ${folder}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async cloneRepoWithoutLFSFiles() {
|
|
||||||
process.chdir(`${CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute}`);
|
|
||||||
if (
|
|
||||||
fs.existsSync(CloudRunnerFolders.repoPathAbsolute) &&
|
|
||||||
!fs.existsSync(path.join(CloudRunnerFolders.repoPathAbsolute, `.git`))
|
|
||||||
) {
|
|
||||||
await CloudRunnerSystem.Run(`rm -r ${CloudRunnerFolders.repoPathAbsolute}`);
|
|
||||||
CloudRunnerLogger.log(`${CloudRunnerFolders.repoPathAbsolute} repo exists, but no git folder, cleaning up`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
BuildParameters.shouldUseRetainedWorkspaceMode(CloudRunner.buildParameters) &&
|
|
||||||
fs.existsSync(path.join(CloudRunnerFolders.repoPathAbsolute, `.git`))
|
|
||||||
) {
|
|
||||||
process.chdir(CloudRunnerFolders.repoPathAbsolute);
|
|
||||||
RemoteClientLogger.log(
|
|
||||||
`${
|
|
||||||
CloudRunnerFolders.repoPathAbsolute
|
|
||||||
} repo exists - skipping clone - retained workspace mode ${BuildParameters.shouldUseRetainedWorkspaceMode(
|
|
||||||
CloudRunner.buildParameters,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
await CloudRunnerSystem.Run(`git fetch && git reset --hard ${CloudRunner.buildParameters.gitSha}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteClientLogger.log(`Initializing source repository for cloning with caching of LFS files`);
|
|
||||||
await CloudRunnerSystem.Run(`git config --global advice.detachedHead false`);
|
|
||||||
RemoteClientLogger.log(`Cloning the repository being built:`);
|
|
||||||
await CloudRunnerSystem.Run(`git config --global filter.lfs.smudge "git-lfs smudge --skip -- %f"`);
|
|
||||||
await CloudRunnerSystem.Run(`git config --global filter.lfs.process "git-lfs filter-process --skip"`);
|
|
||||||
try {
|
|
||||||
await CloudRunnerSystem.Run(
|
|
||||||
`git clone ${CloudRunnerFolders.targetBuildRepoUrl} ${path.basename(CloudRunnerFolders.repoPathAbsolute)}`,
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
process.chdir(CloudRunnerFolders.repoPathAbsolute);
|
|
||||||
await CloudRunnerSystem.Run(`git lfs install`);
|
|
||||||
assert(fs.existsSync(`.git`), 'git folder exists');
|
|
||||||
RemoteClientLogger.log(`${CloudRunner.buildParameters.branch}`);
|
|
||||||
if (CloudRunner.buildParameters.gitSha !== undefined) {
|
|
||||||
await CloudRunnerSystem.Run(`git checkout ${CloudRunner.buildParameters.gitSha}`);
|
|
||||||
} else {
|
|
||||||
await CloudRunnerSystem.Run(`git checkout ${CloudRunner.buildParameters.branch}`);
|
|
||||||
RemoteClientLogger.log(`buildParameter Git Sha is empty`);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(fs.existsSync(path.join(`.git`, `lfs`)), 'LFS folder should not exist before caching');
|
|
||||||
RemoteClientLogger.log(`Checked out ${CloudRunner.buildParameters.branch}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async replaceLargePackageReferencesWithSharedReferences() {
|
|
||||||
CloudRunnerLogger.log(`Use Shared Pkgs ${CloudRunner.buildParameters.useLargePackages}`);
|
|
||||||
GitHub.updateGitHubCheck(`Use Shared Pkgs ${CloudRunner.buildParameters.useLargePackages}`, ``);
|
|
||||||
if (CloudRunner.buildParameters.useLargePackages) {
|
|
||||||
const filePath = path.join(CloudRunnerFolders.projectPathAbsolute, `Packages/manifest.json`);
|
|
||||||
let manifest = fs.readFileSync(filePath, 'utf8');
|
|
||||||
manifest = manifest.replace(/LargeContent/g, '../../../LargeContent');
|
|
||||||
fs.writeFileSync(filePath, manifest);
|
|
||||||
CloudRunnerLogger.log(`Package Manifest \n ${manifest}`);
|
|
||||||
GitHub.updateGitHubCheck(`Package Manifest \n ${manifest}`, ``);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async pullLatestLFS() {
|
|
||||||
process.chdir(CloudRunnerFolders.repoPathAbsolute);
|
|
||||||
await CloudRunnerSystem.Run(`git config --global filter.lfs.smudge "git-lfs smudge -- %f"`);
|
|
||||||
await CloudRunnerSystem.Run(`git config --global filter.lfs.process "git-lfs filter-process"`);
|
|
||||||
if (!CloudRunner.buildParameters.skipLfs) {
|
|
||||||
await CloudRunnerSystem.Run(`git lfs pull`);
|
|
||||||
RemoteClientLogger.log(`pulled latest LFS files`);
|
|
||||||
assert(fs.existsSync(CloudRunnerFolders.lfsFolderAbsolute));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static async handleRetainedWorkspace() {
|
|
||||||
RemoteClientLogger.log(
|
|
||||||
`Retained Workspace: ${BuildParameters.shouldUseRetainedWorkspaceMode(CloudRunner.buildParameters)}`,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
BuildParameters.shouldUseRetainedWorkspaceMode(CloudRunner.buildParameters) &&
|
|
||||||
fs.existsSync(CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute)) &&
|
|
||||||
fs.existsSync(CloudRunnerFolders.ToLinuxFolder(path.join(CloudRunnerFolders.repoPathAbsolute, `.git`)))
|
|
||||||
) {
|
|
||||||
CloudRunnerLogger.log(`Retained Workspace Already Exists!`);
|
|
||||||
process.chdir(CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute));
|
|
||||||
await CloudRunnerSystem.Run(`git fetch`);
|
|
||||||
await CloudRunnerSystem.Run(`git lfs pull`);
|
|
||||||
await CloudRunnerSystem.Run(`git reset --hard "${CloudRunner.buildParameters.gitSha}"`);
|
|
||||||
await CloudRunnerSystem.Run(`git checkout ${CloudRunner.buildParameters.gitSha}`);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
|
|
||||||
export class RemoteClientLogger {
|
|
||||||
public static log(message: string) {
|
|
||||||
CloudRunnerLogger.log(`[Client] ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static logCliError(message: string) {
|
|
||||||
CloudRunnerLogger.log(`[Client][Error] ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static logCliDiagnostic(message: string) {
|
|
||||||
CloudRunnerLogger.log(`[Client][Diagnostic] ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static logWarning(message: string) {
|
|
||||||
CloudRunnerLogger.logWarning(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
import YAML from 'yaml';
|
|
||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import { CustomWorkflow } from '../../workflows/custom-workflow';
|
|
||||||
import { RemoteClientLogger } from '../../remote-client/remote-client-logger';
|
|
||||||
import path from 'node:path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import Input from '../../../input';
|
|
||||||
import CloudRunnerOptions from '../../options/cloud-runner-options';
|
|
||||||
import { ContainerHook as ContainerHook } from './container-hook';
|
|
||||||
import { CloudRunnerStepParameters } from '../../options/cloud-runner-step-parameters';
|
|
||||||
|
|
||||||
export class ContainerHookService {
|
|
||||||
static GetContainerHooksFromFiles(hookLifecycle: string): ContainerHook[] {
|
|
||||||
const results: ContainerHook[] = [];
|
|
||||||
try {
|
|
||||||
const gameCiCustomStepsPath = path.join(process.cwd(), `game-ci`, `container-hooks`);
|
|
||||||
const files = fs.readdirSync(gameCiCustomStepsPath);
|
|
||||||
for (const file of files) {
|
|
||||||
if (!CloudRunnerOptions.containerHookFiles.includes(file.replace(`.yaml`, ``))) {
|
|
||||||
// RemoteClientLogger.log(`Skipping CustomStepFile: ${file}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const fileContents = fs.readFileSync(path.join(gameCiCustomStepsPath, file), `utf8`);
|
|
||||||
const fileContentsObject = ContainerHookService.ParseContainerHooks(fileContents)[0];
|
|
||||||
if (fileContentsObject.hook === hookLifecycle) {
|
|
||||||
results.push(fileContentsObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
RemoteClientLogger.log(`Failed Getting: ${hookLifecycle} \n ${JSON.stringify(error, undefined, 4)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteClientLogger.log(`Active Steps From Files: \n ${JSON.stringify(results, undefined, 4)}`);
|
|
||||||
|
|
||||||
const builtInContainerHooks: ContainerHook[] = ContainerHookService.ParseContainerHooks(
|
|
||||||
`- name: aws-s3-upload-build
|
|
||||||
image: amazon/aws-cli
|
|
||||||
hook: after
|
|
||||||
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 cp /data/cache/$CACHE_KEY/build/build-${CloudRunner.buildParameters.buildGuid}.tar${
|
|
||||||
CloudRunner.buildParameters.useCompressionStrategy ? '.lz4' : ''
|
|
||||||
} s3://${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/$CACHE_KEY/build/build-$BUILD_GUID.tar${
|
|
||||||
CloudRunner.buildParameters.useCompressionStrategy ? '.lz4' : ''
|
|
||||||
}
|
|
||||||
rm /data/cache/$CACHE_KEY/build/build-${CloudRunner.buildParameters.buildGuid}.tar${
|
|
||||||
CloudRunner.buildParameters.useCompressionStrategy ? '.lz4' : ''
|
|
||||||
}
|
|
||||||
secrets:
|
|
||||||
- name: awsAccessKeyId
|
|
||||||
value: ${process.env.AWS_ACCESS_KEY_ID || ``}
|
|
||||||
- name: awsSecretAccessKey
|
|
||||||
value: ${process.env.AWS_SECRET_ACCESS_KEY || ``}
|
|
||||||
- name: awsDefaultRegion
|
|
||||||
value: ${process.env.AWS_REGION || ``}
|
|
||||||
- name: aws-s3-pull-build
|
|
||||||
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 ${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/ || true
|
|
||||||
aws s3 ls ${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/$CACHE_KEY/build || true
|
|
||||||
mkdir -p /data/cache/$CACHE_KEY/build/
|
|
||||||
aws s3 cp s3://${
|
|
||||||
CloudRunner.buildParameters.awsStackName
|
|
||||||
}/cloud-runner-cache/$CACHE_KEY/build/build-$BUILD_GUID_TARGET.tar${
|
|
||||||
CloudRunner.buildParameters.useCompressionStrategy ? '.lz4' : ''
|
|
||||||
} /data/cache/$CACHE_KEY/build/build-$BUILD_GUID_TARGET.tar${
|
|
||||||
CloudRunner.buildParameters.useCompressionStrategy ? '.lz4' : ''
|
|
||||||
}
|
|
||||||
secrets:
|
|
||||||
- name: AWS_ACCESS_KEY_ID
|
|
||||||
- name: AWS_SECRET_ACCESS_KEY
|
|
||||||
- name: AWS_DEFAULT_REGION
|
|
||||||
- name: BUILD_GUID_TARGET
|
|
||||||
- name: steam-deploy-client
|
|
||||||
image: steamcmd/steamcmd
|
|
||||||
commands: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y curl tar coreutils git tree > /dev/null
|
|
||||||
curl -s https://gist.githubusercontent.com/frostebite/1d56f5505b36b403b64193b7a6e54cdc/raw/fa6639ed4ef750c4268ea319d63aa80f52712ffb/deploy-client-steam.sh | bash
|
|
||||||
secrets:
|
|
||||||
- name: STEAM_USERNAME
|
|
||||||
- name: STEAM_PASSWORD
|
|
||||||
- name: STEAM_APPID
|
|
||||||
- name: STEAM_SSFN_FILE_NAME
|
|
||||||
- name: STEAM_SSFN_FILE_CONTENTS
|
|
||||||
- name: STEAM_CONFIG_VDF_1
|
|
||||||
- name: STEAM_CONFIG_VDF_2
|
|
||||||
- name: STEAM_CONFIG_VDF_3
|
|
||||||
- name: STEAM_CONFIG_VDF_4
|
|
||||||
- name: BUILD_GUID_TARGET
|
|
||||||
- name: RELEASE_BRANCH
|
|
||||||
- name: steam-deploy-project
|
|
||||||
image: steamcmd/steamcmd
|
|
||||||
commands: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y curl tar coreutils git tree > /dev/null
|
|
||||||
curl -s https://gist.githubusercontent.com/frostebite/969da6a41002a0e901174124b643709f/raw/02403e53fb292026cba81ddcf4ff35fc1eba111d/steam-deploy-project.sh | bash
|
|
||||||
secrets:
|
|
||||||
- name: STEAM_USERNAME
|
|
||||||
- name: STEAM_PASSWORD
|
|
||||||
- name: STEAM_APPID
|
|
||||||
- name: STEAM_SSFN_FILE_NAME
|
|
||||||
- name: STEAM_SSFN_FILE_CONTENTS
|
|
||||||
- name: STEAM_CONFIG_VDF_1
|
|
||||||
- name: STEAM_CONFIG_VDF_2
|
|
||||||
- name: STEAM_CONFIG_VDF_3
|
|
||||||
- name: STEAM_CONFIG_VDF_4
|
|
||||||
- name: BUILD_GUID_2
|
|
||||||
- name: RELEASE_BRANCH
|
|
||||||
- name: aws-s3-upload-cache
|
|
||||||
image: amazon/aws-cli
|
|
||||||
hook: after
|
|
||||||
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 cp --recursive /data/cache/$CACHE_KEY/lfs s3://${
|
|
||||||
CloudRunner.buildParameters.awsStackName
|
|
||||||
}/cloud-runner-cache/$CACHE_KEY/lfs
|
|
||||||
rm -r /data/cache/$CACHE_KEY/lfs
|
|
||||||
aws s3 cp --recursive /data/cache/$CACHE_KEY/Library s3://${
|
|
||||||
CloudRunner.buildParameters.awsStackName
|
|
||||||
}/cloud-runner-cache/$CACHE_KEY/Library
|
|
||||||
rm -r /data/cache/$CACHE_KEY/Library
|
|
||||||
secrets:
|
|
||||||
- name: AWS_ACCESS_KEY_ID
|
|
||||||
value: ${process.env.AWS_ACCESS_KEY_ID || ``}
|
|
||||||
- name: AWS_SECRET_ACCESS_KEY
|
|
||||||
value: ${process.env.AWS_SECRET_ACCESS_KEY || ``}
|
|
||||||
- name: AWS_DEFAULT_REGION
|
|
||||||
value: ${process.env.AWS_REGION || ``}
|
|
||||||
- name: aws-s3-pull-cache
|
|
||||||
image: amazon/aws-cli
|
|
||||||
hook: before
|
|
||||||
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
|
|
||||||
mkdir -p /data/cache/$CACHE_KEY/Library/
|
|
||||||
mkdir -p /data/cache/$CACHE_KEY/lfs/
|
|
||||||
aws s3 ls ${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/ || true
|
|
||||||
aws s3 ls ${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/$CACHE_KEY/ || true
|
|
||||||
BUCKET1="${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/$CACHE_KEY/Library/"
|
|
||||||
aws s3 ls $BUCKET1 || true
|
|
||||||
OBJECT1="$(aws s3 ls $BUCKET1 | sort | tail -n 1 | awk '{print $4}' || '')"
|
|
||||||
aws s3 cp s3://$BUCKET1$OBJECT1 /data/cache/$CACHE_KEY/Library/ || true
|
|
||||||
BUCKET2="${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/$CACHE_KEY/lfs/"
|
|
||||||
aws s3 ls $BUCKET2 || true
|
|
||||||
OBJECT2="$(aws s3 ls $BUCKET2 | sort | tail -n 1 | awk '{print $4}' || '')"
|
|
||||||
aws s3 cp s3://$BUCKET2$OBJECT2 /data/cache/$CACHE_KEY/lfs/ || true
|
|
||||||
secrets:
|
|
||||||
- name: AWS_ACCESS_KEY_ID
|
|
||||||
value: ${process.env.AWS_ACCESS_KEY_ID || ``}
|
|
||||||
- name: AWS_SECRET_ACCESS_KEY
|
|
||||||
value: ${process.env.AWS_SECRET_ACCESS_KEY || ``}
|
|
||||||
- name: AWS_DEFAULT_REGION
|
|
||||||
value: ${process.env.AWS_REGION || ``}
|
|
||||||
- name: debug-cache
|
|
||||||
image: ubuntu
|
|
||||||
hook: after
|
|
||||||
commands: |
|
|
||||||
apt-get update > /dev/null
|
|
||||||
${CloudRunnerOptions.cloudRunnerDebug ? `apt-get install -y tree > /dev/null` : `#`}
|
|
||||||
${CloudRunnerOptions.cloudRunnerDebug ? `tree -L 3 /data/cache` : `#`}
|
|
||||||
secrets:
|
|
||||||
- name: awsAccessKeyId
|
|
||||||
value: ${process.env.AWS_ACCESS_KEY_ID || ``}
|
|
||||||
- name: awsSecretAccessKey
|
|
||||||
value: ${process.env.AWS_SECRET_ACCESS_KEY || ``}
|
|
||||||
- name: awsDefaultRegion
|
|
||||||
value: ${process.env.AWS_REGION || ``}`,
|
|
||||||
).filter((x) => CloudRunnerOptions.containerHookFiles.includes(x.name) && x.hook === hookLifecycle);
|
|
||||||
if (builtInContainerHooks.length > 0) {
|
|
||||||
results.push(...builtInContainerHooks);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ConvertYamlSecrets(object: ContainerHook) {
|
|
||||||
if (object.secrets === undefined) {
|
|
||||||
object.secrets = [];
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
object.secrets = object.secrets.map((x: { [key: string]: any }) => {
|
|
||||||
return {
|
|
||||||
ParameterKey: x.name,
|
|
||||||
EnvironmentVariable: Input.ToEnvVarFormat(x.name),
|
|
||||||
ParameterValue: x.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ParseContainerHooks(steps: string): ContainerHook[] {
|
|
||||||
if (steps === '') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const isArray = steps.replace(/\s/g, ``)[0] === `-`;
|
|
||||||
const object: ContainerHook[] = isArray ? YAML.parse(steps) : [YAML.parse(steps)];
|
|
||||||
for (const step of object) {
|
|
||||||
ContainerHookService.ConvertYamlSecrets(step);
|
|
||||||
if (step.secrets === undefined) {
|
|
||||||
step.secrets = [];
|
|
||||||
} else {
|
|
||||||
for (const secret of step.secrets) {
|
|
||||||
if (secret.ParameterValue === undefined && process.env[secret.EnvironmentVariable] !== undefined) {
|
|
||||||
if (CloudRunner.buildParameters?.cloudRunnerDebug) {
|
|
||||||
// CloudRunnerLogger.log(`Injecting custom step ${step.name} from env var ${secret.ParameterKey}`);
|
|
||||||
}
|
|
||||||
secret.ParameterValue = process.env[secret.ParameterKey] || ``;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (step.image === undefined) {
|
|
||||||
step.image = `ubuntu`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (object === undefined) {
|
|
||||||
throw new Error(`Failed to parse ${steps}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async RunPostBuildSteps(cloudRunnerStepState: CloudRunnerStepParameters) {
|
|
||||||
let output = ``;
|
|
||||||
const steps: ContainerHook[] = [
|
|
||||||
...ContainerHookService.ParseContainerHooks(CloudRunner.buildParameters.postBuildContainerHooks),
|
|
||||||
...ContainerHookService.GetContainerHooksFromFiles(`after`),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (steps.length > 0) {
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('post build steps');
|
|
||||||
output += await CustomWorkflow.runContainerJob(
|
|
||||||
steps,
|
|
||||||
cloudRunnerStepState.environment,
|
|
||||||
cloudRunnerStepState.secrets,
|
|
||||||
);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
static async RunPreBuildSteps(cloudRunnerStepState: CloudRunnerStepParameters) {
|
|
||||||
let output = ``;
|
|
||||||
const steps: ContainerHook[] = [
|
|
||||||
...ContainerHookService.ParseContainerHooks(CloudRunner.buildParameters.preBuildContainerHooks),
|
|
||||||
...ContainerHookService.GetContainerHooksFromFiles(`before`),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (steps.length > 0) {
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('pre build steps');
|
|
||||||
output += await CustomWorkflow.runContainerJob(
|
|
||||||
steps,
|
|
||||||
cloudRunnerStepState.environment,
|
|
||||||
cloudRunnerStepState.secrets,
|
|
||||||
);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import CloudRunnerSecret from '../../options/cloud-runner-secret';
|
|
||||||
|
|
||||||
export class ContainerHook {
|
|
||||||
public commands!: string;
|
|
||||||
public secrets: CloudRunnerSecret[] = new Array<CloudRunnerSecret>();
|
|
||||||
public name!: string;
|
|
||||||
public image: string = `ubuntu`;
|
|
||||||
public hook!: string;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
import { BuildParameters, ImageTag } from '../..';
|
|
||||||
import UnityVersioning from '../../unity-versioning';
|
|
||||||
import { Cli } from '../../cli/cli';
|
|
||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import CloudRunnerOptions from '../options/cloud-runner-options';
|
|
||||||
import setups from './cloud-runner-suite.test';
|
|
||||||
import { CloudRunnerSystem } from '../services/core/cloud-runner-system';
|
|
||||||
import { OptionValues } from 'commander';
|
|
||||||
|
|
||||||
async function CreateParameters(overrides: OptionValues | undefined) {
|
|
||||||
if (overrides) {
|
|
||||||
Cli.options = overrides;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await BuildParameters.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Cloud Runner pre-built S3 steps', () => {
|
|
||||||
it('Responds', () => {});
|
|
||||||
setups();
|
|
||||||
if (CloudRunnerOptions.cloudRunnerDebug && CloudRunnerOptions.providerStrategy !== `local-docker`) {
|
|
||||||
it('Run build and prebuilt s3 cache pull, cache push and upload build', async () => {
|
|
||||||
const overrides = {
|
|
||||||
versioning: 'None',
|
|
||||||
projectPath: 'test-project',
|
|
||||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
|
||||||
targetPlatform: 'StandaloneLinux64',
|
|
||||||
cacheKey: `test-case-${uuidv4()}`,
|
|
||||||
containerHookFiles: `aws-s3-pull-cache,aws-s3-upload-cache,aws-s3-upload-build`,
|
|
||||||
};
|
|
||||||
const buildParameter2 = await CreateParameters(overrides);
|
|
||||||
const baseImage2 = new ImageTag(buildParameter2);
|
|
||||||
const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
|
|
||||||
CloudRunnerLogger.log(`run 2 succeeded`);
|
|
||||||
|
|
||||||
const build2ContainsBuildSucceeded = results2.includes('Build succeeded');
|
|
||||||
expect(build2ContainsBuildSucceeded).toBeTruthy();
|
|
||||||
|
|
||||||
const results = await CloudRunnerSystem.RunAndReadLines(
|
|
||||||
`aws s3 ls s3://${CloudRunner.buildParameters.awsStackName}/cloud-runner-cache/`,
|
|
||||||
);
|
|
||||||
CloudRunnerLogger.log(results.join(`,`));
|
|
||||||
}, 1_000_000_000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
import { BuildParameters, ImageTag } from '../../..';
|
|
||||||
import UnityVersioning from '../../../unity-versioning';
|
|
||||||
import { Cli } from '../../../cli/cli';
|
|
||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import CloudRunnerOptions from '../../options/cloud-runner-options';
|
|
||||||
import setups from '../cloud-runner-suite.test';
|
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import { CloudRunnerSystem } from '../../services/core/cloud-runner-system';
|
|
||||||
|
|
||||||
async function CreateParameters(overrides: any) {
|
|
||||||
if (overrides) {
|
|
||||||
Cli.options = overrides;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await BuildParameters.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Cloud Runner Caching', () => {
|
|
||||||
it('Responds', () => {});
|
|
||||||
setups();
|
|
||||||
if (CloudRunnerOptions.cloudRunnerDebug) {
|
|
||||||
it('Run one build it should not use cache, run subsequent build which should use cache', async () => {
|
|
||||||
const overrides = {
|
|
||||||
versioning: 'None',
|
|
||||||
projectPath: 'test-project',
|
|
||||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
|
||||||
targetPlatform: 'StandaloneLinux64',
|
|
||||||
cacheKey: `test-case-${uuidv4()}`,
|
|
||||||
containerHookFiles: `debug-cache`,
|
|
||||||
};
|
|
||||||
if (CloudRunnerOptions.providerStrategy === `k8s`) {
|
|
||||||
overrides.containerHookFiles += `,aws-s3-pull-cache,aws-s3-upload-cache`;
|
|
||||||
}
|
|
||||||
const buildParameter = await CreateParameters(overrides);
|
|
||||||
expect(buildParameter.projectPath).toEqual(overrides.projectPath);
|
|
||||||
|
|
||||||
const baseImage = new ImageTag(buildParameter);
|
|
||||||
const results = await CloudRunner.run(buildParameter, baseImage.toString());
|
|
||||||
const libraryString = 'Rebuilding Library because the asset database could not be found!';
|
|
||||||
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
|
|
||||||
const buildSucceededString = 'Build succeeded';
|
|
||||||
|
|
||||||
expect(results).toContain(libraryString);
|
|
||||||
expect(results).toContain(buildSucceededString);
|
|
||||||
expect(results).not.toContain(cachePushFail);
|
|
||||||
|
|
||||||
CloudRunnerLogger.log(`run 1 succeeded`);
|
|
||||||
|
|
||||||
if (CloudRunnerOptions.providerStrategy === `local-docker`) {
|
|
||||||
await CloudRunnerSystem.Run(`tree ./cloud-runner-cache/cache`);
|
|
||||||
await CloudRunnerSystem.Run(
|
|
||||||
`cp ./cloud-runner-cache/cache/${buildParameter.cacheKey}/Library/lib-${buildParameter.buildGuid}.tar ./`,
|
|
||||||
);
|
|
||||||
await CloudRunnerSystem.Run(`mkdir results`);
|
|
||||||
await CloudRunnerSystem.Run(`tar -xf lib-${buildParameter.buildGuid}.tar -C ./results`);
|
|
||||||
await CloudRunnerSystem.Run(`tree -d ./results`);
|
|
||||||
const cacheFolderExists = fs.existsSync(`cloud-runner-cache/cache/${overrides.cacheKey}`);
|
|
||||||
expect(cacheFolderExists).toBeTruthy();
|
|
||||||
}
|
|
||||||
const buildParameter2 = await CreateParameters(overrides);
|
|
||||||
|
|
||||||
buildParameter2.cacheKey = buildParameter.cacheKey;
|
|
||||||
const baseImage2 = new ImageTag(buildParameter2);
|
|
||||||
const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
|
|
||||||
CloudRunnerLogger.log(`run 2 succeeded`);
|
|
||||||
|
|
||||||
const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey);
|
|
||||||
const build2ContainsBuildSucceeded = results2.includes(buildSucceededString);
|
|
||||||
const build2NotContainsNoLibraryMessage = !results2.includes(libraryString);
|
|
||||||
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
|
|
||||||
'There is 0 files/dir in the cache pulled contents for Library',
|
|
||||||
);
|
|
||||||
const build2NotContainsZeroLFSCacheFilesMessage = !results2.includes(
|
|
||||||
'There is 0 files/dir in the cache pulled contents for LFS',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(build2ContainsCacheKey).toBeTruthy();
|
|
||||||
expect(build2ContainsBuildSucceeded).toBeTruthy();
|
|
||||||
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();
|
|
||||||
expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy();
|
|
||||||
expect(build2NotContainsNoLibraryMessage).toBeTruthy();
|
|
||||||
}, 1_000_000_000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import CloudRunner from '../../cloud-runner';
|
|
||||||
import { ImageTag } from '../../..';
|
|
||||||
import UnityVersioning from '../../../unity-versioning';
|
|
||||||
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import CloudRunnerOptions from '../../options/cloud-runner-options';
|
|
||||||
import setups from './../cloud-runner-suite.test';
|
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { CloudRunnerFolders } from '../../options/cloud-runner-folders';
|
|
||||||
import SharedWorkspaceLocking from '../../services/core/shared-workspace-locking';
|
|
||||||
import { CreateParameters } from '../create-test-parameter';
|
|
||||||
import { CloudRunnerSystem } from '../../services/core/cloud-runner-system';
|
|
||||||
|
|
||||||
describe('Cloud Runner Retain Workspace', () => {
|
|
||||||
it('Responds', () => {});
|
|
||||||
setups();
|
|
||||||
if (CloudRunnerOptions.cloudRunnerDebug) {
|
|
||||||
it('Run one build it should not already be retained, run subsequent build which should use retained workspace', async () => {
|
|
||||||
const overrides = {
|
|
||||||
versioning: 'None',
|
|
||||||
projectPath: 'test-project',
|
|
||||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
|
||||||
targetPlatform: 'StandaloneLinux64',
|
|
||||||
cacheKey: `test-case-${uuidv4()}`,
|
|
||||||
maxRetainedWorkspaces: 1,
|
|
||||||
};
|
|
||||||
const buildParameter = await CreateParameters(overrides);
|
|
||||||
expect(buildParameter.projectPath).toEqual(overrides.projectPath);
|
|
||||||
|
|
||||||
const baseImage = new ImageTag(buildParameter);
|
|
||||||
const results = await CloudRunner.run(buildParameter, baseImage.toString());
|
|
||||||
const libraryString = 'Rebuilding Library because the asset database could not be found!';
|
|
||||||
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
|
|
||||||
const buildSucceededString = 'Build succeeded';
|
|
||||||
|
|
||||||
expect(results).toContain(libraryString);
|
|
||||||
expect(results).toContain(buildSucceededString);
|
|
||||||
expect(results).not.toContain(cachePushFail);
|
|
||||||
|
|
||||||
if (CloudRunnerOptions.providerStrategy === `local-docker`) {
|
|
||||||
const cacheFolderExists = fs.existsSync(`cloud-runner-cache/cache/${overrides.cacheKey}`);
|
|
||||||
expect(cacheFolderExists).toBeTruthy();
|
|
||||||
await CloudRunnerSystem.Run(`tree -d ./cloud-runner-cache`);
|
|
||||||
}
|
|
||||||
|
|
||||||
CloudRunnerLogger.log(`run 1 succeeded`);
|
|
||||||
|
|
||||||
// await CloudRunnerSystem.Run(`tree -d ./cloud-runner-cache/${}`);
|
|
||||||
const buildParameter2 = await CreateParameters(overrides);
|
|
||||||
|
|
||||||
buildParameter2.cacheKey = buildParameter.cacheKey;
|
|
||||||
const baseImage2 = new ImageTag(buildParameter2);
|
|
||||||
const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
|
|
||||||
CloudRunnerLogger.log(`run 2 succeeded`);
|
|
||||||
|
|
||||||
const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey);
|
|
||||||
const build2ContainsBuildGuid1FromRetainedWorkspace = results2.includes(buildParameter.buildGuid);
|
|
||||||
const build2ContainsRetainedWorkspacePhrase = results2.includes(`Retained Workspace:`);
|
|
||||||
const build2ContainsWorkspaceExistsAlreadyPhrase = results2.includes(`Retained Workspace Already Exists!`);
|
|
||||||
const build2ContainsBuildSucceeded = results2.includes(buildSucceededString);
|
|
||||||
const build2NotContainsNoLibraryMessage = !results2.includes(libraryString);
|
|
||||||
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
|
|
||||||
'There is 0 files/dir in the cache pulled contents for Library',
|
|
||||||
);
|
|
||||||
const build2NotContainsZeroLFSCacheFilesMessage = !results2.includes(
|
|
||||||
'There is 0 files/dir in the cache pulled contents for LFS',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(build2ContainsCacheKey).toBeTruthy();
|
|
||||||
expect(build2ContainsRetainedWorkspacePhrase).toBeTruthy();
|
|
||||||
expect(build2ContainsWorkspaceExistsAlreadyPhrase).toBeTruthy();
|
|
||||||
expect(build2ContainsBuildGuid1FromRetainedWorkspace).toBeTruthy();
|
|
||||||
expect(build2ContainsBuildSucceeded).toBeTruthy();
|
|
||||||
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();
|
|
||||||
expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy();
|
|
||||||
expect(build2NotContainsNoLibraryMessage).toBeTruthy();
|
|
||||||
}, 1_000_000_000);
|
|
||||||
afterAll(async () => {
|
|
||||||
await SharedWorkspaceLocking.CleanupWorkspace(CloudRunner.lockedWorkspace || ``, CloudRunner.buildParameters);
|
|
||||||
if (
|
|
||||||
fs.existsSync(`./cloud-runner-cache/${path.basename(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute)}`)
|
|
||||||
) {
|
|
||||||
CloudRunnerLogger.log(
|
|
||||||
`Cleaning up ./cloud-runner-cache/${path.basename(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import CloudRunnerSecret from '../options/cloud-runner-secret';
|
|
||||||
import CloudRunnerEnvironmentVariable from '../options/cloud-runner-environment-variable';
|
|
||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
import { CloudRunnerFolders } from '../options/cloud-runner-folders';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
|
|
||||||
export class AsyncWorkflow {
|
|
||||||
public static async runAsyncWorkflow(
|
|
||||||
environmentVariables: CloudRunnerEnvironmentVariable[],
|
|
||||||
secrets: CloudRunnerSecret[],
|
|
||||||
): Promise<string> {
|
|
||||||
try {
|
|
||||||
CloudRunnerLogger.log(`Cloud Runner is running async mode`);
|
|
||||||
const asyncEnvironmentVariable = new CloudRunnerEnvironmentVariable();
|
|
||||||
asyncEnvironmentVariable.name = `ASYNC_WORKFLOW`;
|
|
||||||
asyncEnvironmentVariable.value = `true`;
|
|
||||||
|
|
||||||
let output = '';
|
|
||||||
|
|
||||||
output += await CloudRunner.Provider.runTaskInWorkflow(
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
`ubuntu`,
|
|
||||||
`apt-get update > /dev/null
|
|
||||||
apt-get install -y curl tar tree npm git git-lfs jq git > /dev/null
|
|
||||||
mkdir /builder
|
|
||||||
printenv
|
|
||||||
git config --global advice.detachedHead false
|
|
||||||
git config --global filter.lfs.smudge "git-lfs smudge --skip -- %f"
|
|
||||||
git config --global filter.lfs.process "git-lfs filter-process --skip"
|
|
||||||
git clone -q -b ${CloudRunner.buildParameters.cloudRunnerBranch} ${CloudRunnerFolders.unityBuilderRepoUrl} /builder
|
|
||||||
git clone -q -b ${CloudRunner.buildParameters.branch} ${CloudRunnerFolders.targetBuildRepoUrl} /repo
|
|
||||||
cd /repo
|
|
||||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
||||||
unzip awscliv2.zip
|
|
||||||
./aws/install
|
|
||||||
aws --version
|
|
||||||
node /builder/dist/index.js -m async-workflow`,
|
|
||||||
`/${CloudRunnerFolders.buildVolumeFolder}`,
|
|
||||||
`/${CloudRunnerFolders.buildVolumeFolder}/`,
|
|
||||||
[...environmentVariables, asyncEnvironmentVariable],
|
|
||||||
[
|
|
||||||
...secrets,
|
|
||||||
...[
|
|
||||||
{
|
|
||||||
ParameterKey: `AWS_ACCESS_KEY_ID`,
|
|
||||||
EnvironmentVariable: `AWS_ACCESS_KEY_ID`,
|
|
||||||
ParameterValue: process.env.AWS_ACCESS_KEY_ID || ``,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ParameterKey: `AWS_SECRET_ACCESS_KEY`,
|
|
||||||
EnvironmentVariable: `AWS_SECRET_ACCESS_KEY`,
|
|
||||||
ParameterValue: process.env.AWS_SECRET_ACCESS_KEY || ``,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
import { CloudRunnerFolders } from '../options/cloud-runner-folders';
|
|
||||||
import { CloudRunnerStepParameters } from '../options/cloud-runner-step-parameters';
|
|
||||||
import { WorkflowInterface } from './workflow-interface';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
import { CommandHookService } from '../services/hooks/command-hook-service';
|
|
||||||
import path from 'node:path';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
import { ContainerHookService } from '../services/hooks/container-hook-service';
|
|
||||||
|
|
||||||
export class BuildAutomationWorkflow implements WorkflowInterface {
|
|
||||||
async run(cloudRunnerStepState: CloudRunnerStepParameters) {
|
|
||||||
return await BuildAutomationWorkflow.standardBuildAutomation(cloudRunnerStepState.image, cloudRunnerStepState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async standardBuildAutomation(baseImage: string, cloudRunnerStepState: CloudRunnerStepParameters) {
|
|
||||||
// TODO accept post and pre build steps as yaml files in the repo
|
|
||||||
CloudRunnerLogger.log(`Cloud Runner is running standard build automation`);
|
|
||||||
|
|
||||||
let output = '';
|
|
||||||
|
|
||||||
output += await ContainerHookService.RunPreBuildSteps(cloudRunnerStepState);
|
|
||||||
CloudRunnerLogger.logWithTime('Configurable pre build step(s) time');
|
|
||||||
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('build');
|
|
||||||
CloudRunnerLogger.log(baseImage);
|
|
||||||
CloudRunnerLogger.logLine(` `);
|
|
||||||
CloudRunnerLogger.logLine('Starting build automation job');
|
|
||||||
|
|
||||||
output += await CloudRunner.Provider.runTaskInWorkflow(
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
baseImage.toString(),
|
|
||||||
BuildAutomationWorkflow.BuildWorkflow,
|
|
||||||
`/${CloudRunnerFolders.buildVolumeFolder}`,
|
|
||||||
`/${CloudRunnerFolders.buildVolumeFolder}/`,
|
|
||||||
cloudRunnerStepState.environment,
|
|
||||||
cloudRunnerStepState.secrets,
|
|
||||||
);
|
|
||||||
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
|
|
||||||
CloudRunnerLogger.logWithTime('Build time');
|
|
||||||
|
|
||||||
output += await ContainerHookService.RunPostBuildSteps(cloudRunnerStepState);
|
|
||||||
CloudRunnerLogger.logWithTime('Configurable post build step(s) time');
|
|
||||||
|
|
||||||
CloudRunnerLogger.log(`Cloud Runner finished running standard build automation`);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static get BuildWorkflow() {
|
|
||||||
const setupHooks = CommandHookService.getHooks(CloudRunner.buildParameters.commandHooks).filter((x) =>
|
|
||||||
x.step?.includes(`setup`),
|
|
||||||
);
|
|
||||||
const buildHooks = CommandHookService.getHooks(CloudRunner.buildParameters.commandHooks).filter((x) =>
|
|
||||||
x.step?.includes(`build`),
|
|
||||||
);
|
|
||||||
const builderPath = CloudRunnerFolders.ToLinuxFolder(
|
|
||||||
path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', `index.js`),
|
|
||||||
);
|
|
||||||
|
|
||||||
return `apt-get update > /dev/null
|
|
||||||
apt-get install -y curl tar tree npm git-lfs jq git > /dev/null
|
|
||||||
npm i -g n > /dev/null
|
|
||||||
n 16.15.1 > /dev/null
|
|
||||||
npm --version
|
|
||||||
node --version
|
|
||||||
${setupHooks.filter((x) => x.hook.includes(`before`)).map((x) => x.commands) || ' '}
|
|
||||||
export GITHUB_WORKSPACE="${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute)}"
|
|
||||||
df -H /data/
|
|
||||||
${BuildAutomationWorkflow.setupCommands(builderPath)}
|
|
||||||
${setupHooks.filter((x) => x.hook.includes(`after`)).map((x) => x.commands) || ' '}
|
|
||||||
${buildHooks.filter((x) => x.hook.includes(`before`)).map((x) => x.commands) || ' '}
|
|
||||||
${BuildAutomationWorkflow.BuildCommands(builderPath)}
|
|
||||||
${buildHooks.filter((x) => x.hook.includes(`after`)).map((x) => x.commands) || ' '}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static setupCommands(builderPath: string) {
|
|
||||||
const commands = `mkdir -p ${CloudRunnerFolders.ToLinuxFolder(
|
|
||||||
CloudRunnerFolders.builderPathAbsolute,
|
|
||||||
)} && git clone -q -b ${CloudRunner.buildParameters.cloudRunnerBranch} ${
|
|
||||||
CloudRunnerFolders.unityBuilderRepoUrl
|
|
||||||
} "${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.builderPathAbsolute)}" && chmod +x ${builderPath}`;
|
|
||||||
|
|
||||||
const cloneBuilderCommands = `if [ -e "${CloudRunnerFolders.ToLinuxFolder(
|
|
||||||
CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute,
|
|
||||||
)}" ] && [ -e "${CloudRunnerFolders.ToLinuxFolder(
|
|
||||||
path.join(CloudRunnerFolders.builderPathAbsolute, `.git`),
|
|
||||||
)}" ] ; then echo "Builder Already Exists!" && tree ${
|
|
||||||
CloudRunnerFolders.builderPathAbsolute
|
|
||||||
}; else ${commands} ; fi`;
|
|
||||||
|
|
||||||
return `export GIT_DISCOVERY_ACROSS_FILESYSTEM=1
|
|
||||||
${cloneBuilderCommands}
|
|
||||||
node ${builderPath} -m remote-cli-pre-build`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BuildCommands(builderPath: string) {
|
|
||||||
const distFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist');
|
|
||||||
const ubuntuPlatformsFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', 'platforms', 'ubuntu');
|
|
||||||
|
|
||||||
return `echo "game ci cloud runner initalized"
|
|
||||||
mkdir -p ${`${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectBuildFolderAbsolute)}/build`}
|
|
||||||
cd ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectPathAbsolute)}
|
|
||||||
cp -r "${CloudRunnerFolders.ToLinuxFolder(path.join(distFolder, 'default-build-script'))}" "/UnityBuilderAction"
|
|
||||||
cp -r "${CloudRunnerFolders.ToLinuxFolder(path.join(ubuntuPlatformsFolder, 'entrypoint.sh'))}" "/entrypoint.sh"
|
|
||||||
cp -r "${CloudRunnerFolders.ToLinuxFolder(path.join(ubuntuPlatformsFolder, 'steps'))}" "/steps"
|
|
||||||
chmod -R +x "/entrypoint.sh"
|
|
||||||
chmod -R +x "/steps"
|
|
||||||
echo "game ci start"
|
|
||||||
/entrypoint.sh
|
|
||||||
echo "game ci caching results"
|
|
||||||
chmod +x ${builderPath}
|
|
||||||
node ${builderPath} -m remote-cli-post-build`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import CloudRunnerLogger from '../services/core/cloud-runner-logger';
|
|
||||||
import CloudRunnerSecret from '../options/cloud-runner-secret';
|
|
||||||
import { CloudRunnerFolders } from '../options/cloud-runner-folders';
|
|
||||||
import CloudRunnerEnvironmentVariable from '../options/cloud-runner-environment-variable';
|
|
||||||
import { ContainerHookService } from '../services/hooks/container-hook-service';
|
|
||||||
import { ContainerHook } from '../services/hooks/container-hook';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
|
|
||||||
export class CustomWorkflow {
|
|
||||||
public static async runContainerJobFromString(
|
|
||||||
buildSteps: string,
|
|
||||||
environmentVariables: CloudRunnerEnvironmentVariable[],
|
|
||||||
secrets: CloudRunnerSecret[],
|
|
||||||
): Promise<string> {
|
|
||||||
return await CustomWorkflow.runContainerJob(
|
|
||||||
ContainerHookService.ParseContainerHooks(buildSteps),
|
|
||||||
environmentVariables,
|
|
||||||
secrets,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async runContainerJob(
|
|
||||||
steps: ContainerHook[],
|
|
||||||
environmentVariables: CloudRunnerEnvironmentVariable[],
|
|
||||||
secrets: CloudRunnerSecret[],
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
let output = '';
|
|
||||||
|
|
||||||
// if (CloudRunner.buildParameters?.cloudRunnerDebug) {
|
|
||||||
// CloudRunnerLogger.log(`Custom Job Description \n${JSON.stringify(buildSteps, undefined, 4)}`);
|
|
||||||
// }
|
|
||||||
for (const step of steps) {
|
|
||||||
CloudRunnerLogger.log(`Cloud Runner is running in custom job mode`);
|
|
||||||
output += await CloudRunner.Provider.runTaskInWorkflow(
|
|
||||||
CloudRunner.buildParameters.buildGuid,
|
|
||||||
step.image,
|
|
||||||
step.commands,
|
|
||||||
`/${CloudRunnerFolders.buildVolumeFolder}`,
|
|
||||||
`/${CloudRunnerFolders.projectPathAbsolute}/`,
|
|
||||||
environmentVariables,
|
|
||||||
[...secrets, ...step.secrets],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { CloudRunnerStepParameters } from '../options/cloud-runner-step-parameters';
|
|
||||||
import { CustomWorkflow } from './custom-workflow';
|
|
||||||
import { WorkflowInterface } from './workflow-interface';
|
|
||||||
import { BuildAutomationWorkflow } from './build-automation-workflow';
|
|
||||||
import CloudRunner from '../cloud-runner';
|
|
||||||
import CloudRunnerOptions from '../options/cloud-runner-options';
|
|
||||||
import { AsyncWorkflow } from './async-workflow';
|
|
||||||
|
|
||||||
export class WorkflowCompositionRoot implements WorkflowInterface {
|
|
||||||
async run(cloudRunnerStepState: CloudRunnerStepParameters) {
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
CloudRunnerOptions.asyncCloudRunner &&
|
|
||||||
!CloudRunner.isCloudRunnerAsyncEnvironment &&
|
|
||||||
!CloudRunner.isCloudRunnerEnvironment
|
|
||||||
) {
|
|
||||||
return await AsyncWorkflow.runAsyncWorkflow(cloudRunnerStepState.environment, cloudRunnerStepState.secrets);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CloudRunner.buildParameters.customJob !== '') {
|
|
||||||
return await CustomWorkflow.runContainerJobFromString(
|
|
||||||
CloudRunner.buildParameters.customJob,
|
|
||||||
cloudRunnerStepState.environment,
|
|
||||||
cloudRunnerStepState.secrets,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await new BuildAutomationWorkflow().run(
|
|
||||||
new CloudRunnerStepParameters(
|
|
||||||
cloudRunnerStepState.image.toString(),
|
|
||||||
cloudRunnerStepState.environment,
|
|
||||||
cloudRunnerStepState.secrets,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { CloudRunnerStepParameters } from '../options/cloud-runner-step-parameters';
|
|
||||||
|
|
||||||
export interface WorkflowInterface {
|
|
||||||
run(
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
cloudRunnerStepState: CloudRunnerStepParameters,
|
|
||||||
): Promise<string>;
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,9 @@ class Docker {
|
|||||||
break;
|
break;
|
||||||
case 'win32':
|
case 'win32':
|
||||||
runCommand = this.getWindowsCommand(image, parameters);
|
runCommand = this.getWindowsCommand(image, parameters);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Operation system, ${process.platform}, is not supported yet.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.silent = silent;
|
options.silent = silent;
|
||||||
@@ -52,7 +55,10 @@ class Docker {
|
|||||||
if (!existsSync(githubHome)) mkdirSync(githubHome);
|
if (!existsSync(githubHome)) mkdirSync(githubHome);
|
||||||
const githubWorkflow = path.join(runnerTempPath, '_github_workflow');
|
const githubWorkflow = path.join(runnerTempPath, '_github_workflow');
|
||||||
if (!existsSync(githubWorkflow)) mkdirSync(githubWorkflow);
|
if (!existsSync(githubWorkflow)) mkdirSync(githubWorkflow);
|
||||||
const commandPrefix = image === `alpine` ? `/bin/sh` : `/bin/bash`;
|
|
||||||
|
// Alpine-based images (alpine, rclone/rclone, etc.) don't have /bin/bash, only /bin/sh
|
||||||
|
const isAlpineBasedImage = image === 'alpine' || image.startsWith('rclone/');
|
||||||
|
const commandPrefix = isAlpineBasedImage ? `/bin/sh` : `/bin/bash`;
|
||||||
|
|
||||||
return `docker run \
|
return `docker run \
|
||||||
--workdir ${dockerWorkspacePath} \
|
--workdir ${dockerWorkspacePath} \
|
||||||
@@ -89,6 +95,7 @@ class Docker {
|
|||||||
const {
|
const {
|
||||||
workspace,
|
workspace,
|
||||||
actionFolder,
|
actionFolder,
|
||||||
|
runnerTempPath,
|
||||||
gitPrivateToken,
|
gitPrivateToken,
|
||||||
dockerWorkspacePath,
|
dockerWorkspacePath,
|
||||||
dockerCpuLimit,
|
dockerCpuLimit,
|
||||||
@@ -96,13 +103,18 @@ class Docker {
|
|||||||
dockerIsolationMode,
|
dockerIsolationMode,
|
||||||
} = parameters;
|
} = parameters;
|
||||||
|
|
||||||
|
const githubHome = path.join(runnerTempPath, '_github_home');
|
||||||
|
if (!existsSync(githubHome)) mkdirSync(githubHome);
|
||||||
|
|
||||||
return `docker run \
|
return `docker run \
|
||||||
--workdir c:${dockerWorkspacePath} \
|
--workdir c:${dockerWorkspacePath} \
|
||||||
--rm \
|
--rm \
|
||||||
${ImageEnvironmentFactory.getEnvVarString(parameters)} \
|
${ImageEnvironmentFactory.getEnvVarString(parameters)} \
|
||||||
|
--env BEE_CACHE_DIRECTORY=c:${dockerWorkspacePath}/Library/bee_cache \
|
||||||
--env GITHUB_WORKSPACE=c:${dockerWorkspacePath} \
|
--env GITHUB_WORKSPACE=c:${dockerWorkspacePath} \
|
||||||
${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \
|
${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \
|
||||||
--volume "${workspace}":"c:${dockerWorkspacePath}" \
|
--volume "${workspace}":"c:${dockerWorkspacePath}" \
|
||||||
|
--volume "${githubHome}":"C:/githubhome" \
|
||||||
--volume "c:/regkeys":"c:/regkeys" \
|
--volume "c:/regkeys":"c:/regkeys" \
|
||||||
--volume "C:/Program Files/Microsoft Visual Studio":"C:/Program Files/Microsoft Visual Studio" \
|
--volume "C:/Program Files/Microsoft Visual Studio":"C:/Program Files/Microsoft Visual Studio" \
|
||||||
--volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \
|
--volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \
|
||||||
@@ -110,6 +122,7 @@ class Docker {
|
|||||||
--volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \
|
--volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \
|
||||||
--volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \
|
--volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \
|
||||||
--volume "${actionFolder}/platforms/windows":"c:/steps" \
|
--volume "${actionFolder}/platforms/windows":"c:/steps" \
|
||||||
|
--volume "${actionFolder}/unity-config":"C:/ProgramData/Unity/config" \
|
||||||
--volume "${actionFolder}/BlankProject":"c:/BlankProject" \
|
--volume "${actionFolder}/BlankProject":"c:/BlankProject" \
|
||||||
--cpus=${dockerCpuLimit} \
|
--cpus=${dockerCpuLimit} \
|
||||||
--memory=${dockerMemoryLimit} \
|
--memory=${dockerMemoryLimit} \
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import CloudRunnerLogger from './cloud-runner/services/core/cloud-runner-logger';
|
import OrchestratorLogger from './orchestrator/services/core/orchestrator-logger';
|
||||||
import CloudRunner from './cloud-runner/cloud-runner';
|
import Orchestrator from './orchestrator/orchestrator';
|
||||||
import CloudRunnerOptions from './cloud-runner/options/cloud-runner-options';
|
import OrchestratorOptions from './orchestrator/options/orchestrator-options';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import { Octokit } from '@octokit/core';
|
import { Octokit } from '@octokit/core';
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ class GitHub {
|
|||||||
private static startedDate: string;
|
private static startedDate: string;
|
||||||
private static endedDate: string;
|
private static endedDate: string;
|
||||||
static result: string = ``;
|
static result: string = ``;
|
||||||
|
static forceAsyncTest: boolean;
|
||||||
private static get octokitDefaultToken() {
|
private static get octokitDefaultToken() {
|
||||||
return new Octokit({
|
return new Octokit({
|
||||||
auth: process.env.GITHUB_TOKEN,
|
auth: process.env.GITHUB_TOKEN,
|
||||||
@@ -18,15 +19,15 @@ class GitHub {
|
|||||||
}
|
}
|
||||||
private static get octokitPAT() {
|
private static get octokitPAT() {
|
||||||
return new Octokit({
|
return new Octokit({
|
||||||
auth: CloudRunner.buildParameters.gitPrivateToken,
|
auth: Orchestrator.buildParameters.gitPrivateToken,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private static get sha() {
|
private static get sha() {
|
||||||
return CloudRunner.buildParameters.gitSha;
|
return Orchestrator.buildParameters.gitSha;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static get checkName() {
|
private static get checkName() {
|
||||||
return `Cloud Runner (${CloudRunner.buildParameters.buildGuid})`;
|
return `Orchestrator (${Orchestrator.buildParameters.buildGuid})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static get nameReadable() {
|
private static get nameReadable() {
|
||||||
@@ -34,24 +35,24 @@ class GitHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static get checkRunId() {
|
private static get checkRunId() {
|
||||||
return CloudRunner.buildParameters.githubCheckId;
|
return Orchestrator.buildParameters.githubCheckId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static get owner() {
|
private static get owner() {
|
||||||
return CloudRunnerOptions.githubOwner;
|
return OrchestratorOptions.githubOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static get repo() {
|
private static get repo() {
|
||||||
return CloudRunnerOptions.githubRepoName;
|
return OrchestratorOptions.githubRepoName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async createGitHubCheck(summary: string) {
|
public static async createGitHubCheck(summary: string) {
|
||||||
if (!CloudRunner.buildParameters.githubChecks) {
|
if (!Orchestrator.buildParameters.githubChecks) {
|
||||||
return ``;
|
return ``;
|
||||||
}
|
}
|
||||||
GitHub.startedDate = new Date().toISOString();
|
GitHub.startedDate = new Date().toISOString();
|
||||||
|
|
||||||
CloudRunnerLogger.log(`Creating inital github check`);
|
OrchestratorLogger.log(`Creating github check`);
|
||||||
const data = {
|
const data = {
|
||||||
owner: GitHub.owner,
|
owner: GitHub.owner,
|
||||||
repo: GitHub.repo,
|
repo: GitHub.repo,
|
||||||
@@ -60,7 +61,7 @@ class GitHub {
|
|||||||
head_sha: GitHub.sha,
|
head_sha: GitHub.sha,
|
||||||
status: 'queued',
|
status: 'queued',
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
external_id: CloudRunner.buildParameters.buildGuid,
|
external_id: Orchestrator.buildParameters.buildGuid,
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
started_at: GitHub.startedDate,
|
started_at: GitHub.startedDate,
|
||||||
output: {
|
output: {
|
||||||
@@ -78,6 +79,8 @@ class GitHub {
|
|||||||
};
|
};
|
||||||
const result = await GitHub.createGitHubCheckRequest(data);
|
const result = await GitHub.createGitHubCheckRequest(data);
|
||||||
|
|
||||||
|
OrchestratorLogger.log(`Creating github check ${result.status}`);
|
||||||
|
|
||||||
return result.data.id.toString();
|
return result.data.id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +90,11 @@ class GitHub {
|
|||||||
result = `neutral`,
|
result = `neutral`,
|
||||||
status = `in_progress`,
|
status = `in_progress`,
|
||||||
) {
|
) {
|
||||||
if (`${CloudRunner.buildParameters.githubChecks}` !== `true`) {
|
if (`${Orchestrator.buildParameters.githubChecks}` !== `true`) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CloudRunnerLogger.log(
|
OrchestratorLogger.log(
|
||||||
`githubChecks: ${CloudRunner.buildParameters.githubChecks} checkRunId: ${GitHub.checkRunId} sha: ${GitHub.sha} async: ${CloudRunner.isCloudRunnerAsyncEnvironment}`,
|
`githubChecks: ${Orchestrator.buildParameters.githubChecks} checkRunId: ${GitHub.checkRunId} sha: ${GitHub.sha} async: ${Orchestrator.isOrchestratorAsyncEnvironment}`,
|
||||||
);
|
);
|
||||||
GitHub.longDescriptionContent += `\n${longDescription}`;
|
GitHub.longDescriptionContent += `\n${longDescription}`;
|
||||||
if (GitHub.result !== `success` && GitHub.result !== `failure`) {
|
if (GitHub.result !== `success` && GitHub.result !== `failure`) {
|
||||||
@@ -127,7 +130,7 @@ class GitHub {
|
|||||||
data.conclusion = result;
|
data.conclusion = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
await (CloudRunner.isCloudRunnerAsyncEnvironment
|
await (Orchestrator.isOrchestratorAsyncEnvironment || GitHub.forceAsyncTest
|
||||||
? GitHub.runUpdateAsyncChecksWorkflow(data, `update`)
|
? GitHub.runUpdateAsyncChecksWorkflow(data, `update`)
|
||||||
: GitHub.updateGitHubCheckRequest(data));
|
: GitHub.updateGitHubCheckRequest(data));
|
||||||
}
|
}
|
||||||
@@ -149,7 +152,7 @@ class GitHub {
|
|||||||
repo: GitHub.repo,
|
repo: GitHub.repo,
|
||||||
});
|
});
|
||||||
const workflows = workflowsResult.data.workflows;
|
const workflows = workflowsResult.data.workflows;
|
||||||
CloudRunnerLogger.log(`Got ${workflows.length} workflows`);
|
OrchestratorLogger.log(`Got ${workflows.length} workflows`);
|
||||||
let selectedId = ``;
|
let selectedId = ``;
|
||||||
for (let index = 0; index < workflowsResult.data.total_count; index++) {
|
for (let index = 0; index < workflowsResult.data.total_count; index++) {
|
||||||
if (workflows[index].name === GitHub.asyncChecksApiWorkflowName) {
|
if (workflows[index].name === GitHub.asyncChecksApiWorkflowName) {
|
||||||
@@ -165,7 +168,7 @@ class GitHub {
|
|||||||
repo: GitHub.repo,
|
repo: GitHub.repo,
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
workflow_id: selectedId,
|
workflow_id: selectedId,
|
||||||
ref: CloudRunnerOptions.branch,
|
ref: OrchestratorOptions.branch,
|
||||||
inputs: {
|
inputs: {
|
||||||
checksObject: JSON.stringify({ data, mode }),
|
checksObject: JSON.stringify({ data, mode }),
|
||||||
},
|
},
|
||||||
@@ -173,39 +176,47 @@ class GitHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async triggerWorkflowOnComplete(triggerWorkflowOnComplete: string[]) {
|
static async triggerWorkflowOnComplete(triggerWorkflowOnComplete: string[]) {
|
||||||
const isLocalAsync = CloudRunner.buildParameters.asyncWorkflow && !CloudRunner.isCloudRunnerAsyncEnvironment;
|
const isLocalAsync = Orchestrator.buildParameters.asyncWorkflow && !Orchestrator.isOrchestratorAsyncEnvironment;
|
||||||
if (isLocalAsync) {
|
if (isLocalAsync || triggerWorkflowOnComplete === undefined || triggerWorkflowOnComplete.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const workflowsResult = await GitHub.octokitPAT.request(`GET /repos/{owner}/{repo}/actions/workflows`, {
|
try {
|
||||||
owner: GitHub.owner,
|
const workflowsResult = await GitHub.octokitPAT.request(`GET /repos/{owner}/{repo}/actions/workflows`, {
|
||||||
repo: GitHub.repo,
|
|
||||||
});
|
|
||||||
const workflows = workflowsResult.data.workflows;
|
|
||||||
CloudRunnerLogger.log(`Got ${workflows.length} workflows`);
|
|
||||||
for (const element of triggerWorkflowOnComplete) {
|
|
||||||
let selectedId = ``;
|
|
||||||
for (let index = 0; index < workflowsResult.data.total_count; index++) {
|
|
||||||
if (workflows[index].name === element) {
|
|
||||||
selectedId = workflows[index].id.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedId === ``) {
|
|
||||||
core.info(JSON.stringify(workflows));
|
|
||||||
throw new Error(`no workflow with name "${GitHub.asyncChecksApiWorkflowName}"`);
|
|
||||||
}
|
|
||||||
await GitHub.octokitPAT.request(`POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches`, {
|
|
||||||
owner: GitHub.owner,
|
owner: GitHub.owner,
|
||||||
repo: GitHub.repo,
|
repo: GitHub.repo,
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
workflow_id: selectedId,
|
|
||||||
ref: CloudRunnerOptions.branch,
|
|
||||||
inputs: {
|
|
||||||
buildGuid: CloudRunner.buildParameters.buildGuid,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
const workflows = workflowsResult.data.workflows;
|
||||||
|
OrchestratorLogger.log(`Got ${workflows.length} workflows`);
|
||||||
|
for (const element of triggerWorkflowOnComplete) {
|
||||||
|
let selectedId = ``;
|
||||||
|
for (let index = 0; index < workflowsResult.data.total_count; index++) {
|
||||||
|
if (workflows[index].name === element) {
|
||||||
|
selectedId = workflows[index].id.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedId === ``) {
|
||||||
|
core.info(JSON.stringify(workflows));
|
||||||
|
throw new Error(`no workflow with name "${GitHub.asyncChecksApiWorkflowName}"`);
|
||||||
|
}
|
||||||
|
await GitHub.octokitPAT.request(`POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches`, {
|
||||||
|
owner: GitHub.owner,
|
||||||
|
repo: GitHub.repo,
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
workflow_id: selectedId,
|
||||||
|
ref: OrchestratorOptions.branch,
|
||||||
|
inputs: {
|
||||||
|
buildGuid: Orchestrator.buildParameters.buildGuid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
core.info(`github workflow complete hook not found`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getCheckStatus() {
|
||||||
|
return await GitHub.octokitDefaultToken.request(`GET /repos/{owner}/{repo}/check-runs/{check_run_id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GitHub;
|
export default GitHub;
|
||||||
|
|||||||
@@ -5,16 +5,17 @@ class ImageEnvironmentFactory {
|
|||||||
const environmentVariables = ImageEnvironmentFactory.getEnvironmentVariables(parameters, additionalVariables);
|
const environmentVariables = ImageEnvironmentFactory.getEnvironmentVariables(parameters, additionalVariables);
|
||||||
let string = '';
|
let string = '';
|
||||||
for (const p of environmentVariables) {
|
for (const p of environmentVariables) {
|
||||||
if (p.value === '' || p.value === undefined) {
|
if (p.value === '' || p.value === undefined || p.value === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (p.name !== 'ANDROID_KEYSTORE_BASE64' && p.value.toString().includes(`\n`)) {
|
const valueAsString = typeof p.value === 'string' ? p.value : String(p.value);
|
||||||
|
if (p.name !== 'ANDROID_KEYSTORE_BASE64' && valueAsString.includes(`\n`)) {
|
||||||
string += `--env ${p.name} `;
|
string += `--env ${p.name} `;
|
||||||
process.env[p.name] = p.value.toString();
|
process.env[p.name] = valueAsString;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string += `--env ${p.name}="${p.value}" `;
|
string += `--env ${p.name}="${valueAsString}" `;
|
||||||
}
|
}
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
@@ -29,18 +30,21 @@ class ImageEnvironmentFactory {
|
|||||||
name: 'UNITY_LICENSING_SERVER',
|
name: 'UNITY_LICENSING_SERVER',
|
||||||
value: parameters.unityLicensingServer,
|
value: parameters.unityLicensingServer,
|
||||||
},
|
},
|
||||||
|
{ name: 'SKIP_ACTIVATION', value: parameters.skipActivation },
|
||||||
{ name: 'UNITY_VERSION', value: parameters.editorVersion },
|
{ name: 'UNITY_VERSION', value: parameters.editorVersion },
|
||||||
{
|
{
|
||||||
name: 'USYM_UPLOAD_AUTH_TOKEN',
|
name: 'USYM_UPLOAD_AUTH_TOKEN',
|
||||||
value: process.env.USYM_UPLOAD_AUTH_TOKEN,
|
value: process.env.USYM_UPLOAD_AUTH_TOKEN,
|
||||||
},
|
},
|
||||||
{ name: 'PROJECT_PATH', value: parameters.projectPath },
|
{ name: 'PROJECT_PATH', value: parameters.projectPath },
|
||||||
|
{ name: 'BUILD_PROFILE', value: parameters.buildProfile },
|
||||||
{ name: 'BUILD_TARGET', value: parameters.targetPlatform },
|
{ name: 'BUILD_TARGET', value: parameters.targetPlatform },
|
||||||
{ name: 'BUILD_NAME', value: parameters.buildName },
|
{ name: 'BUILD_NAME', value: parameters.buildName },
|
||||||
{ name: 'BUILD_PATH', value: parameters.buildPath },
|
{ name: 'BUILD_PATH', value: parameters.buildPath },
|
||||||
{ name: 'BUILD_FILE', value: parameters.buildFile },
|
{ name: 'BUILD_FILE', value: parameters.buildFile },
|
||||||
{ name: 'BUILD_METHOD', value: parameters.buildMethod },
|
{ name: 'BUILD_METHOD', value: parameters.buildMethod },
|
||||||
{ name: 'MANUAL_EXIT', value: parameters.manualExit },
|
{ name: 'MANUAL_EXIT', value: parameters.manualExit },
|
||||||
|
{ name: 'ENABLE_GPU', value: parameters.enableGpu },
|
||||||
{ name: 'VERSION', value: parameters.buildVersion },
|
{ name: 'VERSION', value: parameters.buildVersion },
|
||||||
{ name: 'ANDROID_VERSION_CODE', value: parameters.androidVersionCode },
|
{ name: 'ANDROID_VERSION_CODE', value: parameters.androidVersionCode },
|
||||||
{ name: 'ANDROID_KEYSTORE_NAME', value: parameters.androidKeystoreName },
|
{ name: 'ANDROID_KEYSTORE_NAME', value: parameters.androidKeystoreName },
|
||||||
@@ -79,25 +83,12 @@ class ImageEnvironmentFactory {
|
|||||||
{ name: 'RUNNER_TEMP', value: process.env.RUNNER_TEMP },
|
{ name: 'RUNNER_TEMP', value: process.env.RUNNER_TEMP },
|
||||||
{ name: 'RUNNER_WORKSPACE', value: process.env.RUNNER_WORKSPACE },
|
{ name: 'RUNNER_WORKSPACE', value: process.env.RUNNER_WORKSPACE },
|
||||||
];
|
];
|
||||||
if (parameters.providerStrategy === 'local-docker') {
|
|
||||||
for (const element of additionalVariables) {
|
// Always merge additional variables (e.g., secrets/env from Orchestrator) uniquely by name
|
||||||
if (
|
for (const element of additionalVariables) {
|
||||||
environmentVariables.find(
|
if (!element || !element.name) continue;
|
||||||
(x) => element !== undefined && element.name !== undefined && x.name === element.name,
|
environmentVariables = environmentVariables.filter((x) => x?.name !== element.name);
|
||||||
) === undefined
|
environmentVariables.push(element);
|
||||||
) {
|
|
||||||
environmentVariables.push(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const variable of environmentVariables) {
|
|
||||||
if (
|
|
||||||
environmentVariables.find(
|
|
||||||
(x) => variable !== undefined && variable.name !== undefined && x.name === variable.name,
|
|
||||||
) === undefined
|
|
||||||
) {
|
|
||||||
environmentVariables = environmentVariables.filter((x) => x !== variable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (parameters.sshAgent) {
|
if (parameters.sshAgent) {
|
||||||
environmentVariables.push({ name: 'SSH_AUTH_SOCK', value: '/ssh-agent' });
|
environmentVariables.push({ name: 'SSH_AUTH_SOCK', value: '/ssh-agent' });
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import ImageTag from './image-tag';
|
|||||||
|
|
||||||
describe('ImageTag', () => {
|
describe('ImageTag', () => {
|
||||||
const testImageParameters = {
|
const testImageParameters = {
|
||||||
editorVersion: '2099.9.f9f9',
|
editorVersion: '2099.9.9f9',
|
||||||
targetPlatform: 'Test',
|
targetPlatform: 'Test',
|
||||||
builderPlatform: '',
|
builderPlatform: '',
|
||||||
containerRegistryRepository: 'unityci/editor',
|
containerRegistryRepository: 'unityci/editor',
|
||||||
@@ -27,7 +27,7 @@ describe('ImageTag', () => {
|
|||||||
expect(image.builderPlatform).toStrictEqual(testImageParameters.builderPlatform);
|
expect(image.builderPlatform).toStrictEqual(testImageParameters.builderPlatform);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', (version) => {
|
test.each(['2000.0.0f0', '2011.1.11f1', '6000.0.0f1'])('accepts %p version format', (version) => {
|
||||||
expect(
|
expect(
|
||||||
() =>
|
() =>
|
||||||
new ImageTag({
|
new ImageTag({
|
||||||
@@ -50,23 +50,23 @@ describe('ImageTag', () => {
|
|||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
it('returns the correct version', () => {
|
it('returns the correct version', () => {
|
||||||
const image = new ImageTag({
|
const image = new ImageTag({
|
||||||
editorVersion: '2099.1.1111',
|
editorVersion: '2099.1.1111f1',
|
||||||
targetPlatform: testImageParameters.targetPlatform,
|
targetPlatform: testImageParameters.targetPlatform,
|
||||||
containerRegistryRepository: 'unityci/editor',
|
containerRegistryRepository: 'unityci/editor',
|
||||||
containerRegistryImageVersion: '3',
|
containerRegistryImageVersion: '3',
|
||||||
});
|
});
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'win32':
|
case 'win32':
|
||||||
expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2099.1.1111-3`);
|
expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2099.1.1111f1-3`);
|
||||||
break;
|
break;
|
||||||
case 'linux':
|
case 'linux':
|
||||||
expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2099.1.1111-3`);
|
expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2099.1.1111f1-3`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('returns customImage if given', () => {
|
it('returns customImage if given', () => {
|
||||||
const image = new ImageTag({
|
const image = new ImageTag({
|
||||||
editorVersion: '2099.1.1111',
|
editorVersion: '2099.1.1111f1',
|
||||||
targetPlatform: testImageParameters.targetPlatform,
|
targetPlatform: testImageParameters.targetPlatform,
|
||||||
customImage: `${defaults.image}:2099.1.1111@347598437689743986`,
|
customImage: `${defaults.image}:2099.1.1111@347598437689743986`,
|
||||||
containerRegistryRepository: 'unityci/editor',
|
containerRegistryRepository: 'unityci/editor',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import Platform from './platform';
|
|||||||
|
|
||||||
class ImageTag {
|
class ImageTag {
|
||||||
public repository: string;
|
public repository: string;
|
||||||
public cloudRunnerBuilderPlatform!: string;
|
|
||||||
public editorVersion: string;
|
public editorVersion: string;
|
||||||
public targetPlatform: string;
|
public targetPlatform: string;
|
||||||
public builderPlatform: string;
|
public builderPlatform: string;
|
||||||
@@ -15,9 +14,10 @@ class ImageTag {
|
|||||||
editorVersion,
|
editorVersion,
|
||||||
targetPlatform,
|
targetPlatform,
|
||||||
customImage,
|
customImage,
|
||||||
cloudRunnerBuilderPlatform,
|
buildPlatform,
|
||||||
containerRegistryRepository,
|
containerRegistryRepository,
|
||||||
containerRegistryImageVersion,
|
containerRegistryImageVersion,
|
||||||
|
providerStrategy,
|
||||||
} = imageProperties;
|
} = imageProperties;
|
||||||
|
|
||||||
if (!ImageTag.versionPattern.test(editorVersion)) {
|
if (!ImageTag.versionPattern.test(editorVersion)) {
|
||||||
@@ -32,17 +32,17 @@ class ImageTag {
|
|||||||
this.repository = containerRegistryRepository;
|
this.repository = containerRegistryRepository;
|
||||||
this.editorVersion = editorVersion;
|
this.editorVersion = editorVersion;
|
||||||
this.targetPlatform = targetPlatform;
|
this.targetPlatform = targetPlatform;
|
||||||
this.cloudRunnerBuilderPlatform = cloudRunnerBuilderPlatform;
|
this.builderPlatform = ImageTag.getTargetPlatformToTargetPlatformSuffixMap(
|
||||||
const isCloudRunnerLocal = cloudRunnerBuilderPlatform === 'local' || cloudRunnerBuilderPlatform === undefined;
|
targetPlatform,
|
||||||
this.builderPlatform = ImageTag.getTargetPlatformToTargetPlatformSuffixMap(targetPlatform, editorVersion);
|
editorVersion,
|
||||||
this.imagePlatformPrefix = ImageTag.getImagePlatformPrefixes(
|
providerStrategy,
|
||||||
isCloudRunnerLocal ? process.platform : cloudRunnerBuilderPlatform,
|
|
||||||
);
|
);
|
||||||
|
this.imagePlatformPrefix = ImageTag.getImagePlatformPrefixes(buildPlatform);
|
||||||
this.imageRollingVersion = Number(containerRegistryImageVersion); // Will automatically roll to the latest non-breaking version.
|
this.imageRollingVersion = Number(containerRegistryImageVersion); // Will automatically roll to the latest non-breaking version.
|
||||||
}
|
}
|
||||||
|
|
||||||
static get versionPattern(): RegExp {
|
static get versionPattern(): RegExp {
|
||||||
return /^(20\d{2}\.\d\.\w{3,4}|3)$/;
|
return /^\d+\.\d+\.\d+[a-z]\d+$/;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get targetPlatformSuffixes() {
|
static get targetPlatformSuffixes() {
|
||||||
@@ -58,11 +58,16 @@ class ImageTag {
|
|||||||
android: 'android',
|
android: 'android',
|
||||||
ios: 'ios',
|
ios: 'ios',
|
||||||
tvos: 'appletv',
|
tvos: 'appletv',
|
||||||
|
visionos: 'visionos',
|
||||||
facebook: 'facebook',
|
facebook: 'facebook',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getImagePlatformPrefixes(platform: string): string {
|
static getImagePlatformPrefixes(platform: string): string {
|
||||||
|
if (!platform || platform === '') {
|
||||||
|
platform = process.platform;
|
||||||
|
}
|
||||||
|
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case 'win32':
|
case 'win32':
|
||||||
return 'windows';
|
return 'windows';
|
||||||
@@ -73,9 +78,26 @@ class ImageTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTargetPlatformToTargetPlatformSuffixMap(platform: string, version: string): string {
|
static getTargetPlatformToTargetPlatformSuffixMap(
|
||||||
const { generic, webgl, mac, windows, windowsIl2cpp, wsaPlayer, linux, linuxIl2cpp, android, ios, tvos, facebook } =
|
platform: string,
|
||||||
ImageTag.targetPlatformSuffixes;
|
version: string,
|
||||||
|
providerStrategy: string,
|
||||||
|
): string {
|
||||||
|
const {
|
||||||
|
generic,
|
||||||
|
webgl,
|
||||||
|
mac,
|
||||||
|
windows,
|
||||||
|
windowsIl2cpp,
|
||||||
|
wsaPlayer,
|
||||||
|
linux,
|
||||||
|
linuxIl2cpp,
|
||||||
|
android,
|
||||||
|
ios,
|
||||||
|
tvos,
|
||||||
|
visionos,
|
||||||
|
facebook,
|
||||||
|
} = ImageTag.targetPlatformSuffixes;
|
||||||
|
|
||||||
const [major, minor] = version.split('.').map((digit) => Number(digit));
|
const [major, minor] = version.split('.').map((digit) => Number(digit));
|
||||||
|
|
||||||
@@ -102,7 +124,11 @@ class ImageTag {
|
|||||||
case Platform.types.StandaloneLinux64: {
|
case Platform.types.StandaloneLinux64: {
|
||||||
// Unity versions before 2019.3 do not support il2cpp
|
// Unity versions before 2019.3 do not support il2cpp
|
||||||
if (major >= 2020 || (major === 2019 && minor >= 3)) {
|
if (major >= 2020 || (major === 2019 && minor >= 3)) {
|
||||||
return linuxIl2cpp;
|
if (providerStrategy === 'local') {
|
||||||
|
return linuxIl2cpp;
|
||||||
|
} else {
|
||||||
|
return process.env.USE_IL2CPP === 'true' ? linuxIl2cpp : linux;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return linux;
|
return linux;
|
||||||
@@ -124,11 +150,17 @@ class ImageTag {
|
|||||||
case Platform.types.XboxOne:
|
case Platform.types.XboxOne:
|
||||||
return windows;
|
return windows;
|
||||||
case Platform.types.tvOS:
|
case Platform.types.tvOS:
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
||||||
throw new Error(`tvOS can only be built on a windows base OS`);
|
throw new Error(`tvOS can only be built on Windows or macOS base OS`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tvos;
|
return tvos;
|
||||||
|
case Platform.types.VisionOS:
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
throw new Error(`visionOS can only be built on a macOS base OS`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return visionos;
|
||||||
case Platform.types.Switch:
|
case Platform.types.Switch:
|
||||||
return windows;
|
return windows;
|
||||||
|
|
||||||
@@ -169,7 +201,7 @@ class ImageTag {
|
|||||||
|
|
||||||
if (customImage) return customImage;
|
if (customImage) return customImage;
|
||||||
|
|
||||||
return `${image}:${tag}`; // '0' here represents the docker repo version
|
return `${image}:${tag}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import Platform from './platform';
|
|||||||
import Project from './project';
|
import Project from './project';
|
||||||
import Unity from './unity';
|
import Unity from './unity';
|
||||||
import Versioning from './versioning';
|
import Versioning from './versioning';
|
||||||
import CloudRunner from './cloud-runner/cloud-runner';
|
import Orchestrator from './orchestrator/orchestrator';
|
||||||
|
import loadProvider, { ProviderLoader } from './orchestrator/providers/provider-loader';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Action,
|
Action,
|
||||||
@@ -23,5 +24,7 @@ export {
|
|||||||
Project,
|
Project,
|
||||||
Unity,
|
Unity,
|
||||||
Versioning,
|
Versioning,
|
||||||
CloudRunner as CloudRunner,
|
Orchestrator as Orchestrator,
|
||||||
|
loadProvider,
|
||||||
|
ProviderLoader,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { CloudRunnerSystem } from '../cloud-runner/services/core/cloud-runner-system';
|
import { OrchestratorSystem } from '../orchestrator/services/core/orchestrator-system';
|
||||||
import CloudRunnerOptions from '../cloud-runner/options/cloud-runner-options';
|
import OrchestratorOptions from '../orchestrator/options/orchestrator-options';
|
||||||
|
|
||||||
export class GenericInputReader {
|
export class GenericInputReader {
|
||||||
public static async Run(command: string) {
|
public static async Run(command: string) {
|
||||||
if (CloudRunnerOptions.providerStrategy === 'local') {
|
if (OrchestratorOptions.providerStrategy === 'local') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return await CloudRunnerSystem.Run(command, false, true);
|
return await OrchestratorSystem.Run(command, false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GitRepoReader } from './git-repo';
|
import { GitRepoReader } from './git-repo';
|
||||||
import { CloudRunnerSystem } from '../cloud-runner/services/core/cloud-runner-system';
|
import { OrchestratorSystem } from '../orchestrator/services/core/orchestrator-system';
|
||||||
import CloudRunnerOptions from '../cloud-runner/options/cloud-runner-options';
|
import OrchestratorOptions from '../orchestrator/options/orchestrator-options';
|
||||||
|
|
||||||
describe(`git repo tests`, () => {
|
describe(`git repo tests`, () => {
|
||||||
it(`Branch value parsed from CLI to not contain illegal characters`, async () => {
|
it(`Branch value parsed from CLI to not contain illegal characters`, async () => {
|
||||||
@@ -10,15 +10,15 @@ describe(`git repo tests`, () => {
|
|||||||
|
|
||||||
it(`returns valid branch name when using https`, async () => {
|
it(`returns valid branch name when using https`, async () => {
|
||||||
const mockValue = 'https://github.com/example/example.git';
|
const mockValue = 'https://github.com/example/example.git';
|
||||||
await jest.spyOn(CloudRunnerSystem, 'Run').mockReturnValue(Promise.resolve(mockValue));
|
await jest.spyOn(OrchestratorSystem, 'Run').mockReturnValue(Promise.resolve(mockValue));
|
||||||
await jest.spyOn(CloudRunnerOptions, 'providerStrategy', 'get').mockReturnValue('not-local');
|
await jest.spyOn(OrchestratorOptions, 'providerStrategy', 'get').mockReturnValue('not-local');
|
||||||
expect(await GitRepoReader.GetRemote()).toEqual(`example/example`);
|
expect(await GitRepoReader.GetRemote()).toEqual(`example/example`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`returns valid branch name when using ssh`, async () => {
|
it(`returns valid branch name when using ssh`, async () => {
|
||||||
const mockValue = 'git@github.com:example/example.git';
|
const mockValue = 'git@github.com:example/example.git';
|
||||||
await jest.spyOn(CloudRunnerSystem, 'Run').mockReturnValue(Promise.resolve(mockValue));
|
await jest.spyOn(OrchestratorSystem, 'Run').mockReturnValue(Promise.resolve(mockValue));
|
||||||
await jest.spyOn(CloudRunnerOptions, 'providerStrategy', 'get').mockReturnValue('not-local');
|
await jest.spyOn(OrchestratorOptions, 'providerStrategy', 'get').mockReturnValue('not-local');
|
||||||
expect(await GitRepoReader.GetRemote()).toEqual(`example/example`);
|
expect(await GitRepoReader.GetRemote()).toEqual(`example/example`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import { assert } from 'node:console';
|
import { assert } from 'node:console';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { CloudRunnerSystem } from '../cloud-runner/services/core/cloud-runner-system';
|
import { OrchestratorSystem } from '../orchestrator/services/core/orchestrator-system';
|
||||||
import CloudRunnerLogger from '../cloud-runner/services/core/cloud-runner-logger';
|
import OrchestratorLogger from '../orchestrator/services/core/orchestrator-logger';
|
||||||
import CloudRunnerOptions from '../cloud-runner/options/cloud-runner-options';
|
import OrchestratorOptions from '../orchestrator/options/orchestrator-options';
|
||||||
import Input from '../input';
|
import Input from '../input';
|
||||||
|
|
||||||
export class GitRepoReader {
|
export class GitRepoReader {
|
||||||
public static async GetRemote() {
|
public static async GetRemote() {
|
||||||
if (CloudRunnerOptions.providerStrategy === 'local') {
|
if (OrchestratorOptions.providerStrategy === 'local') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
assert(fs.existsSync(`.git`));
|
assert(fs.existsSync(`.git`));
|
||||||
const value = (await CloudRunnerSystem.Run(`cd ${Input.projectPath} && git remote -v`, false, true)).replace(
|
const value = (await OrchestratorSystem.Run(`cd ${Input.projectPath} && git remote -v`, false, true)).replace(
|
||||||
/ /g,
|
/ /g,
|
||||||
``,
|
``,
|
||||||
);
|
);
|
||||||
CloudRunnerLogger.log(`value ${value}`);
|
OrchestratorLogger.log(`value ${value}`);
|
||||||
assert(value.includes('github.com'));
|
assert(value.includes('github.com'));
|
||||||
|
|
||||||
return value.split('github.com')[1].split('.git')[0].slice(1);
|
return value.split('github.com')[1].split('.git')[0].slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async GetBranch() {
|
public static async GetBranch() {
|
||||||
if (CloudRunnerOptions.providerStrategy === 'local') {
|
if (OrchestratorOptions.providerStrategy === 'local') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
assert(fs.existsSync(`.git`));
|
assert(fs.existsSync(`.git`));
|
||||||
|
|
||||||
return (await CloudRunnerSystem.Run(`cd ${Input.projectPath} && git branch --show-current`, false, true))
|
return (await OrchestratorSystem.Run(`cd ${Input.projectPath} && git branch --show-current`, false, true))
|
||||||
.split('\n')[0]
|
.split('\n')[0]
|
||||||
.replace(/ /g, ``)
|
.replace(/ /g, ``)
|
||||||
.replace('/head', '');
|
.replace('/head', '');
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { CloudRunnerSystem } from '../cloud-runner/services/core/cloud-runner-system';
|
import { OrchestratorSystem } from '../orchestrator/services/core/orchestrator-system';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import CloudRunnerOptions from '../cloud-runner/options/cloud-runner-options';
|
import OrchestratorOptions from '../orchestrator/options/orchestrator-options';
|
||||||
|
|
||||||
export class GithubCliReader {
|
export class GithubCliReader {
|
||||||
static async GetGitHubAuthToken() {
|
static async GetGitHubAuthToken() {
|
||||||
if (CloudRunnerOptions.providerStrategy === 'local') {
|
if (OrchestratorOptions.providerStrategy === 'local') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const authStatus = await CloudRunnerSystem.Run(`gh auth status`, true, true);
|
const authStatus = await OrchestratorSystem.Run(`gh auth status`, true, true);
|
||||||
if (authStatus.includes('You are not logged') || authStatus === '') {
|
if (authStatus.includes('You are not logged') || authStatus === '') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await CloudRunnerSystem.Run(`gh auth status -t`, false, true))
|
return (await OrchestratorSystem.Run(`gh auth status -t`, false, true))
|
||||||
.split(`Token: `)[1]
|
.split(`Token: `)[1]
|
||||||
.replace(/ /g, '')
|
.replace(/ /g, '')
|
||||||
.replace(/\n/g, '');
|
.replace(/\n/g, '');
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import YAML from 'yaml';
|
import YAML from 'yaml';
|
||||||
import CloudRunnerOptions from '../cloud-runner/options/cloud-runner-options';
|
import OrchestratorOptions from '../orchestrator/options/orchestrator-options';
|
||||||
|
|
||||||
export function ReadLicense(): string {
|
export function ReadLicense(): string {
|
||||||
if (CloudRunnerOptions.providerStrategy === 'local') {
|
if (OrchestratorOptions.providerStrategy === 'local') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const pipelineFile = path.join(__dirname, `.github`, `workflows`, `cloud-runner-k8s-pipeline.yml`);
|
const pipelineFile = path.join(__dirname, `.github`, `workflows`, `orchestrator-k8s-pipeline.yml`);
|
||||||
|
|
||||||
return fs.existsSync(pipelineFile) ? YAML.parse(fs.readFileSync(pipelineFile, 'utf8')).env.UNITY_LICENSE : '';
|
return fs.existsSync(pipelineFile) ? YAML.parse(fs.readFileSync(pipelineFile, 'utf8')).env.UNITY_LICENSE : '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,19 @@ describe('Input', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('buildProfile', () => {
|
||||||
|
it('returns the default value', () => {
|
||||||
|
expect(Input.buildProfile).toStrictEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('takes input from the users workflow', () => {
|
||||||
|
const mockValue = 'path/to/build_profile.asset';
|
||||||
|
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
|
||||||
|
expect(Input.buildProfile).toStrictEqual(mockValue);
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('buildName', () => {
|
describe('buildName', () => {
|
||||||
it('returns the default value', () => {
|
it('returns the default value', () => {
|
||||||
expect(Input.buildName).toStrictEqual(Input.targetPlatform);
|
expect(Input.buildName).toStrictEqual(Input.targetPlatform);
|
||||||
@@ -122,6 +135,24 @@ describe('Input', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('enableGpu', () => {
|
||||||
|
it('returns the default value', () => {
|
||||||
|
expect(Input.enableGpu).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true when string true is passed', () => {
|
||||||
|
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
|
||||||
|
expect(Input.enableGpu).toStrictEqual(true);
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when string false is passed', () => {
|
||||||
|
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
|
||||||
|
expect(Input.enableGpu).toStrictEqual(false);
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('versioningStrategy', () => {
|
describe('versioningStrategy', () => {
|
||||||
it('returns the default value', () => {
|
it('returns the default value', () => {
|
||||||
expect(Input.versioningStrategy).toStrictEqual('Semantic');
|
expect(Input.versioningStrategy).toStrictEqual('Semantic');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { Cli } from './cli/cli';
|
import { Cli } from './cli/cli';
|
||||||
import CloudRunnerQueryOverride from './cloud-runner/options/cloud-runner-query-override';
|
import OrchestratorQueryOverride from './orchestrator/options/orchestrator-query-override';
|
||||||
import Platform from './platform';
|
import Platform from './platform';
|
||||||
import GitHub from './github';
|
import GitHub from './github';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
@@ -32,8 +32,8 @@ class Input {
|
|||||||
return Cli.query(query, alternativeQuery);
|
return Cli.query(query, alternativeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CloudRunnerQueryOverride.query(query, alternativeQuery)) {
|
if (OrchestratorQueryOverride.query(query, alternativeQuery)) {
|
||||||
return CloudRunnerQueryOverride.query(query, alternativeQuery);
|
return OrchestratorQueryOverride.query(query, alternativeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env[query] !== undefined) {
|
if (process.env[query] !== undefined) {
|
||||||
@@ -46,11 +46,11 @@ class Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get region(): string {
|
static get region(): string {
|
||||||
return Input.getInput('region') || 'eu-west-2';
|
return Input.getInput('region') ?? 'eu-west-2';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get githubRepo(): string | undefined {
|
static get githubRepo(): string | undefined {
|
||||||
return Input.getInput('GITHUB_REPOSITORY') || Input.getInput('GITHUB_REPO') || undefined;
|
return Input.getInput('GITHUB_REPOSITORY') ?? Input.getInput('GITHUB_REPO') ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get branch(): string {
|
static get branch(): string {
|
||||||
@@ -74,19 +74,19 @@ class Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get runNumber(): string {
|
static get runNumber(): string {
|
||||||
return Input.getInput('GITHUB_RUN_NUMBER') || '0';
|
return Input.getInput('GITHUB_RUN_NUMBER') ?? '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get targetPlatform(): string {
|
static get targetPlatform(): string {
|
||||||
return Input.getInput('targetPlatform') || Platform.default;
|
return Input.getInput('targetPlatform') ?? Platform.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get unityVersion(): string {
|
static get unityVersion(): string {
|
||||||
return Input.getInput('unityVersion') || 'auto';
|
return Input.getInput('unityVersion') ?? 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get customImage(): string {
|
static get customImage(): string {
|
||||||
return Input.getInput('customImage') || '';
|
return Input.getInput('customImage') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get projectPath(): string {
|
static get projectPath(): string {
|
||||||
@@ -107,86 +107,96 @@ class Input {
|
|||||||
return rawProjectPath.replace(/\/$/, '');
|
return rawProjectPath.replace(/\/$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get buildProfile(): string {
|
||||||
|
return Input.getInput('buildProfile') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
static get runnerTempPath(): string {
|
static get runnerTempPath(): string {
|
||||||
return Input.getInput('RUNNER_TEMP') || '';
|
return Input.getInput('RUNNER_TEMP') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get buildName(): string {
|
static get buildName(): string {
|
||||||
return Input.getInput('buildName') || Input.targetPlatform;
|
return Input.getInput('buildName') ?? Input.targetPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get buildsPath(): string {
|
static get buildsPath(): string {
|
||||||
return Input.getInput('buildsPath') || 'build';
|
return Input.getInput('buildsPath') ?? 'build';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get unityLicensingServer(): string {
|
static get unityLicensingServer(): string {
|
||||||
return Input.getInput('unityLicensingServer') || '';
|
return Input.getInput('unityLicensingServer') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get buildMethod(): string {
|
static get buildMethod(): string {
|
||||||
return Input.getInput('buildMethod') || ''; // Processed in docker file
|
return Input.getInput('buildMethod') ?? ''; // Processed in docker file
|
||||||
}
|
}
|
||||||
|
|
||||||
static get manualExit(): boolean {
|
static get manualExit(): boolean {
|
||||||
const input = Input.getInput('manualExit') || false;
|
const input = Input.getInput('manualExit') ?? false;
|
||||||
|
|
||||||
|
return input === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get enableGpu(): boolean {
|
||||||
|
const input = Input.getInput('enableGpu') ?? false;
|
||||||
|
|
||||||
return input === 'true';
|
return input === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get customParameters(): string {
|
static get customParameters(): string {
|
||||||
return Input.getInput('customParameters') || '';
|
return Input.getInput('customParameters') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get versioningStrategy(): string {
|
static get versioningStrategy(): string {
|
||||||
return Input.getInput('versioning') || 'Semantic';
|
return Input.getInput('versioning') ?? 'Semantic';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get specifiedVersion(): string {
|
static get specifiedVersion(): string {
|
||||||
return Input.getInput('version') || '';
|
return Input.getInput('version') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidVersionCode(): string {
|
static get androidVersionCode(): string {
|
||||||
return Input.getInput('androidVersionCode') || '';
|
return Input.getInput('androidVersionCode') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidExportType(): string {
|
static get androidExportType(): string {
|
||||||
return Input.getInput('androidExportType') || 'androidPackage';
|
return Input.getInput('androidExportType') ?? 'androidPackage';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidKeystoreName(): string {
|
static get androidKeystoreName(): string {
|
||||||
return Input.getInput('androidKeystoreName') || '';
|
return Input.getInput('androidKeystoreName') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidKeystoreBase64(): string {
|
static get androidKeystoreBase64(): string {
|
||||||
return Input.getInput('androidKeystoreBase64') || '';
|
return Input.getInput('androidKeystoreBase64') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidKeystorePass(): string {
|
static get androidKeystorePass(): string {
|
||||||
return Input.getInput('androidKeystorePass') || '';
|
return Input.getInput('androidKeystorePass') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidKeyaliasName(): string {
|
static get androidKeyaliasName(): string {
|
||||||
return Input.getInput('androidKeyaliasName') || '';
|
return Input.getInput('androidKeyaliasName') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidKeyaliasPass(): string {
|
static get androidKeyaliasPass(): string {
|
||||||
return Input.getInput('androidKeyaliasPass') || '';
|
return Input.getInput('androidKeyaliasPass') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidTargetSdkVersion(): string {
|
static get androidTargetSdkVersion(): string {
|
||||||
return Input.getInput('androidTargetSdkVersion') || '';
|
return Input.getInput('androidTargetSdkVersion') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get androidSymbolType(): string {
|
static get androidSymbolType(): string {
|
||||||
return Input.getInput('androidSymbolType') || 'none';
|
return Input.getInput('androidSymbolType') ?? 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get sshAgent(): string {
|
static get sshAgent(): string {
|
||||||
return Input.getInput('sshAgent') || '';
|
return Input.getInput('sshAgent') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get sshPublicKeysDirectoryPath(): string {
|
static get sshPublicKeysDirectoryPath(): string {
|
||||||
return Input.getInput('sshPublicKeysDirectoryPath') || '';
|
return Input.getInput('sshPublicKeysDirectoryPath') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get gitPrivateToken(): string | undefined {
|
static get gitPrivateToken(): string | undefined {
|
||||||
@@ -194,27 +204,27 @@ class Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get runAsHostUser(): string {
|
static get runAsHostUser(): string {
|
||||||
return Input.getInput('runAsHostUser') || 'false';
|
return Input.getInput('runAsHostUser')?.toLowerCase() ?? 'false';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get chownFilesTo() {
|
static get chownFilesTo() {
|
||||||
return Input.getInput('chownFilesTo') || '';
|
return Input.getInput('chownFilesTo') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get allowDirtyBuild(): boolean {
|
static get allowDirtyBuild(): boolean {
|
||||||
const input = Input.getInput('allowDirtyBuild') || false;
|
const input = Input.getInput('allowDirtyBuild') ?? false;
|
||||||
|
|
||||||
return input === 'true';
|
return input === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get cacheUnityInstallationOnMac(): boolean {
|
static get cacheUnityInstallationOnMac(): boolean {
|
||||||
const input = Input.getInput('cacheUnityInstallationOnMac') || false;
|
const input = Input.getInput('cacheUnityInstallationOnMac') ?? false;
|
||||||
|
|
||||||
return input === 'true';
|
return input === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get unityHubVersionOnMac(): string {
|
static get unityHubVersionOnMac(): string {
|
||||||
const input = Input.getInput('unityHubVersionOnMac') || '';
|
const input = Input.getInput('unityHubVersionOnMac') ?? '';
|
||||||
|
|
||||||
return input !== '' ? input : '';
|
return input !== '' ? input : '';
|
||||||
}
|
}
|
||||||
@@ -228,11 +238,11 @@ class Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get dockerWorkspacePath(): string {
|
static get dockerWorkspacePath(): string {
|
||||||
return Input.getInput('dockerWorkspacePath') || '/github/workspace';
|
return Input.getInput('dockerWorkspacePath') ?? '/github/workspace';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get dockerCpuLimit(): string {
|
static get dockerCpuLimit(): string {
|
||||||
return Input.getInput('dockerCpuLimit') || os.cpus().length.toString();
|
return Input.getInput('dockerCpuLimit') ?? os.cpus().length.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get dockerMemoryLimit(): string {
|
static get dockerMemoryLimit(): string {
|
||||||
@@ -252,20 +262,24 @@ class Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Input.getInput('dockerMemoryLimit') || `${Math.floor((os.totalmem() / bytesInMegabyte) * memoryMultiplier)}m`
|
Input.getInput('dockerMemoryLimit') ?? `${Math.floor((os.totalmem() / bytesInMegabyte) * memoryMultiplier)}m`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get dockerIsolationMode(): string {
|
static get dockerIsolationMode(): string {
|
||||||
return Input.getInput('dockerIsolationMode') || 'default';
|
return Input.getInput('dockerIsolationMode') ?? 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get containerRegistryRepository(): string {
|
static get containerRegistryRepository(): string {
|
||||||
return Input.getInput('containerRegistryRepository')!;
|
return Input.getInput('containerRegistryRepository') ?? 'unityci/editor';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get containerRegistryImageVersion(): string {
|
static get containerRegistryImageVersion(): string {
|
||||||
return Input.getInput('containerRegistryImageVersion')!;
|
return Input.getInput('containerRegistryImageVersion') ?? '3';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get skipActivation(): string {
|
||||||
|
return Input.getInput('skipActivation')?.toLowerCase() ?? 'false';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ToEnvVarFormat(input: string) {
|
public static ToEnvVarFormat(input: string) {
|
||||||
|
|||||||
15
src/model/orchestrator/error/orchestrator-error.ts
Normal file
15
src/model/orchestrator/error/orchestrator-error.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import OrchestratorLogger from '../services/core/orchestrator-logger';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import Orchestrator from '../orchestrator';
|
||||||
|
import OrchestratorSecret from '../options/orchestrator-secret';
|
||||||
|
import BuildParameters from '../../build-parameters';
|
||||||
|
|
||||||
|
export class OrchestratorError {
|
||||||
|
public static async handleException(error: unknown, buildParameters: BuildParameters, secrets: OrchestratorSecret[]) {
|
||||||
|
OrchestratorLogger.error(JSON.stringify(error, undefined, 4));
|
||||||
|
core.setFailed('Orchestrator failed');
|
||||||
|
if (Orchestrator.Provider !== undefined) {
|
||||||
|
await Orchestrator.Provider.cleanupWorkflow(buildParameters, buildParameters.branch, secrets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/model/orchestrator/options/orchestrator-constants.ts
Normal file
4
src/model/orchestrator/options/orchestrator-constants.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class OrchestratorConstants {
|
||||||
|
static alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
|
}
|
||||||
|
export default OrchestratorConstants;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class OrchestratorEnvironmentVariable {
|
||||||
|
public name!: string;
|
||||||
|
public value!: string;
|
||||||
|
}
|
||||||
|
export default OrchestratorEnvironmentVariable;
|
||||||
140
src/model/orchestrator/options/orchestrator-folders-auth.test.ts
Normal file
140
src/model/orchestrator/options/orchestrator-folders-auth.test.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { OrchestratorFolders } from './orchestrator-folders';
|
||||||
|
|
||||||
|
jest.mock('../orchestrator', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
buildParameters: {
|
||||||
|
orchestratorRepoName: 'game-ci/unity-builder',
|
||||||
|
githubRepo: 'myorg/myrepo',
|
||||||
|
gitPrivateToken: 'ghp_test123',
|
||||||
|
gitAuthMode: 'header',
|
||||||
|
buildGuid: 'test-guid',
|
||||||
|
projectPath: '',
|
||||||
|
buildPath: 'Builds',
|
||||||
|
cacheKey: 'test-cache',
|
||||||
|
},
|
||||||
|
lockedWorkspace: '',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('./orchestrator-options', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
useSharedBuilder: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../services/core/orchestrator-system', () => ({
|
||||||
|
OrchestratorSystem: {
|
||||||
|
Run: jest.fn().mockResolvedValue(''),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockOrchestrator = require('../orchestrator').default;
|
||||||
|
|
||||||
|
describe('OrchestratorFolders git auth', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useHeaderAuth', () => {
|
||||||
|
it('should return true when gitAuthMode is header', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'header';
|
||||||
|
expect(OrchestratorFolders.useHeaderAuth).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when gitAuthMode is undefined (default)', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = undefined;
|
||||||
|
expect(OrchestratorFolders.useHeaderAuth).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when gitAuthMode is url', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'url';
|
||||||
|
expect(OrchestratorFolders.useHeaderAuth).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('unityBuilderRepoUrl', () => {
|
||||||
|
it('should not include token in URL when using header auth', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'header';
|
||||||
|
const url = OrchestratorFolders.unityBuilderRepoUrl;
|
||||||
|
expect(url).toBe('https://github.com/game-ci/unity-builder.git');
|
||||||
|
expect(url).not.toContain('ghp_test123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include token in URL when using url auth (legacy)', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'url';
|
||||||
|
const url = OrchestratorFolders.unityBuilderRepoUrl;
|
||||||
|
expect(url).toBe('https://ghp_test123@github.com/game-ci/unity-builder.git');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('targetBuildRepoUrl', () => {
|
||||||
|
it('should not include token in URL when using header auth', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'header';
|
||||||
|
const url = OrchestratorFolders.targetBuildRepoUrl;
|
||||||
|
expect(url).toBe('https://github.com/myorg/myrepo.git');
|
||||||
|
expect(url).not.toContain('ghp_test123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include token in URL when using url auth (legacy)', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'url';
|
||||||
|
const url = OrchestratorFolders.targetBuildRepoUrl;
|
||||||
|
expect(url).toBe('https://ghp_test123@github.com/myorg/myrepo.git');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('gitAuthConfigScript', () => {
|
||||||
|
it('should emit http.extraHeader commands in header mode', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'header';
|
||||||
|
const script = OrchestratorFolders.gitAuthConfigScript;
|
||||||
|
expect(script).toContain('http.extraHeader');
|
||||||
|
expect(script).toContain('GIT_PRIVATE_TOKEN');
|
||||||
|
expect(script).toContain('Authorization: Basic');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit no-op comment in url mode', () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'url';
|
||||||
|
const script = OrchestratorFolders.gitAuthConfigScript;
|
||||||
|
expect(script).toContain('legacy');
|
||||||
|
expect(script).not.toContain('http.extraHeader');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('configureGitAuth', () => {
|
||||||
|
it('should run git config with http.extraHeader in header mode', async () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'header';
|
||||||
|
mockOrchestrator.buildParameters.gitPrivateToken = 'ghp_test123';
|
||||||
|
const { OrchestratorSystem } = require('../services/core/orchestrator-system');
|
||||||
|
|
||||||
|
await OrchestratorFolders.configureGitAuth();
|
||||||
|
|
||||||
|
// Verify the base64 encoding and extraHeader config are correct
|
||||||
|
const expectedEncoded = Buffer.from('x-access-token:ghp_test123').toString('base64');
|
||||||
|
expect(OrchestratorSystem.Run).toHaveBeenCalledWith(expect.stringContaining(expectedEncoded));
|
||||||
|
expect(OrchestratorSystem.Run).toHaveBeenCalledWith(expect.stringContaining('.extraHeader'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run git config in url mode', async () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'url';
|
||||||
|
const { OrchestratorSystem } = require('../services/core/orchestrator-system');
|
||||||
|
|
||||||
|
await OrchestratorFolders.configureGitAuth();
|
||||||
|
|
||||||
|
expect(OrchestratorSystem.Run).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run git config when no token is available', async () => {
|
||||||
|
mockOrchestrator.buildParameters.gitAuthMode = 'header';
|
||||||
|
mockOrchestrator.buildParameters.gitPrivateToken = '';
|
||||||
|
const originalEnv = process.env.GIT_PRIVATE_TOKEN;
|
||||||
|
delete process.env.GIT_PRIVATE_TOKEN;
|
||||||
|
const { OrchestratorSystem } = require('../services/core/orchestrator-system');
|
||||||
|
|
||||||
|
await OrchestratorFolders.configureGitAuth();
|
||||||
|
|
||||||
|
expect(OrchestratorSystem.Run).not.toHaveBeenCalled();
|
||||||
|
if (originalEnv !== undefined) process.env.GIT_PRIVATE_TOKEN = originalEnv;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
143
src/model/orchestrator/options/orchestrator-folders.ts
Normal file
143
src/model/orchestrator/options/orchestrator-folders.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import OrchestratorOptions from './orchestrator-options';
|
||||||
|
import Orchestrator from '../orchestrator';
|
||||||
|
import BuildParameters from '../../build-parameters';
|
||||||
|
|
||||||
|
export class OrchestratorFolders {
|
||||||
|
public static readonly repositoryFolder = 'repo';
|
||||||
|
|
||||||
|
public static ToLinuxFolder(folder: string) {
|
||||||
|
return folder.replace(/\\/g, `/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the following paths that do not start a path.join with another "Full" suffixed property need to start with an absolute /
|
||||||
|
|
||||||
|
public static get uniqueOrchestratorJobFolderAbsolute(): string {
|
||||||
|
return Orchestrator.buildParameters && BuildParameters.shouldUseRetainedWorkspaceMode(Orchestrator.buildParameters)
|
||||||
|
? path.join(`/`, OrchestratorFolders.buildVolumeFolder, Orchestrator.lockedWorkspace)
|
||||||
|
: path.join(`/`, OrchestratorFolders.buildVolumeFolder, Orchestrator.buildParameters.buildGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get cacheFolderForAllFull(): string {
|
||||||
|
return path.join('/', OrchestratorFolders.buildVolumeFolder, OrchestratorFolders.cacheFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get cacheFolderForCacheKeyFull(): string {
|
||||||
|
return path.join(
|
||||||
|
'/',
|
||||||
|
OrchestratorFolders.buildVolumeFolder,
|
||||||
|
OrchestratorFolders.cacheFolder,
|
||||||
|
Orchestrator.buildParameters.cacheKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get builderPathAbsolute(): string {
|
||||||
|
return path.join(
|
||||||
|
OrchestratorOptions.useSharedBuilder
|
||||||
|
? `/${OrchestratorFolders.buildVolumeFolder}`
|
||||||
|
: OrchestratorFolders.uniqueOrchestratorJobFolderAbsolute,
|
||||||
|
`builder`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get repoPathAbsolute(): string {
|
||||||
|
return path.join(OrchestratorFolders.uniqueOrchestratorJobFolderAbsolute, OrchestratorFolders.repositoryFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get projectPathAbsolute(): string {
|
||||||
|
return path.join(OrchestratorFolders.repoPathAbsolute, Orchestrator.buildParameters.projectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get libraryFolderAbsolute(): string {
|
||||||
|
return path.join(OrchestratorFolders.projectPathAbsolute, `Library`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get projectBuildFolderAbsolute(): string {
|
||||||
|
return path.join(OrchestratorFolders.repoPathAbsolute, Orchestrator.buildParameters.buildPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get lfsFolderAbsolute(): string {
|
||||||
|
return path.join(OrchestratorFolders.repoPathAbsolute, `.git`, `lfs`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get purgeRemoteCaching(): boolean {
|
||||||
|
return process.env.PURGE_REMOTE_BUILDER_CACHE !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get lfsCacheFolderFull() {
|
||||||
|
return path.join(OrchestratorFolders.cacheFolderForCacheKeyFull, `lfs`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get libraryCacheFolderFull() {
|
||||||
|
return path.join(OrchestratorFolders.cacheFolderForCacheKeyFull, `Library`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use http.extraHeader for git authentication (secure, default)
|
||||||
|
* instead of embedding the token in clone URLs (legacy).
|
||||||
|
*/
|
||||||
|
public static get useHeaderAuth(): boolean {
|
||||||
|
return Orchestrator.buildParameters.gitAuthMode !== 'url';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get unityBuilderRepoUrl(): string {
|
||||||
|
if (OrchestratorFolders.useHeaderAuth) {
|
||||||
|
return `https://github.com/${Orchestrator.buildParameters.orchestratorRepoName}.git`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `https://${Orchestrator.buildParameters.gitPrivateToken}@github.com/${Orchestrator.buildParameters.orchestratorRepoName}.git`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get targetBuildRepoUrl(): string {
|
||||||
|
if (OrchestratorFolders.useHeaderAuth) {
|
||||||
|
return `https://github.com/${Orchestrator.buildParameters.githubRepo}.git`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `https://${Orchestrator.buildParameters.gitPrivateToken}@github.com/${Orchestrator.buildParameters.githubRepo}.git`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shell commands to configure git authentication via http.extraHeader.
|
||||||
|
* Uses GIT_PRIVATE_TOKEN env var so the token never appears in clone URLs or git config output.
|
||||||
|
* This is the same mechanism used by actions/checkout.
|
||||||
|
*
|
||||||
|
* Only emits commands when gitAuthMode is 'header' (default). In 'url' mode,
|
||||||
|
* returns a no-op comment since the token is already in the URL.
|
||||||
|
*/
|
||||||
|
public static get gitAuthConfigScript(): string {
|
||||||
|
if (!OrchestratorFolders.useHeaderAuth) {
|
||||||
|
return `# git auth: using token-in-URL mode (legacy)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `# git auth: configuring http.extraHeader (secure mode)
|
||||||
|
if [ -n "$GIT_PRIVATE_TOKEN" ]; then
|
||||||
|
git config --global http.https://github.com/.extraHeader "Authorization: Basic $(printf '%s' "x-access-token:$GIT_PRIVATE_TOKEN" | base64 -w 0)"
|
||||||
|
fi`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure git authentication via http.extraHeader in the current Node process.
|
||||||
|
* For use in the remote-client where shell scripts aren't used.
|
||||||
|
* Only configures when gitAuthMode is 'header' (default).
|
||||||
|
*/
|
||||||
|
public static async configureGitAuth(): Promise<void> {
|
||||||
|
if (!OrchestratorFolders.useHeaderAuth) return;
|
||||||
|
|
||||||
|
const token = Orchestrator.buildParameters.gitPrivateToken || process.env.GIT_PRIVATE_TOKEN || '';
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
const encoded = Buffer.from(`x-access-token:${token}`).toString('base64');
|
||||||
|
const { OrchestratorSystem } = await import('../services/core/orchestrator-system');
|
||||||
|
await OrchestratorSystem.Run(
|
||||||
|
`git config --global http.https://github.com/.extraHeader "Authorization: Basic ${encoded}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get buildVolumeFolder() {
|
||||||
|
return 'data';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get cacheFolder() {
|
||||||
|
return 'cache';
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user