style: format changed files with prettier

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
frostebite
2026-03-05 07:52:11 +00:00
parent f4bc5d20c4
commit d17b099593
5 changed files with 915 additions and 87 deletions
+40 -50
View File
@@ -182,8 +182,8 @@ inputs:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] Run a custom job instead of the standard build automation for orchestrator (in yaml format with the '[Orchestrator] Run a custom job instead of the standard build automation for orchestrator (in yaml format with
keys image, secrets (name, value object array), command line string)' the keys image, secrets (name, value object array), command line string)'
awsStackName: awsStackName:
default: 'game-ci' default: 'game-ci'
required: false required: false
@@ -283,126 +283,116 @@ inputs:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] [Experimental] Google Cloud project ID for Cloud Run Jobs provider. '[Orchestrator] [Experimental] Google Cloud project ID for Cloud Run Jobs provider. Falls back to
Falls back to GOOGLE_CLOUD_PROJECT env var.' GOOGLE_CLOUD_PROJECT env var.'
gcpRegion: gcpRegion:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] [Experimental] Google Cloud region for Cloud Run Jobs (e.g. us-central1). '[Orchestrator] [Experimental] Google Cloud region for Cloud Run Jobs (e.g. us-central1). Defaults to the region
Defaults to the region input if empty.' input if empty.'
gcpStorageType: gcpStorageType:
required: false required: false
default: 'gcs-fuse' default: 'gcs-fuse'
description: description:
'[Orchestrator] [Experimental] Storage type for Cloud Run Jobs. Options: '[Orchestrator] [Experimental] Storage type for Cloud Run Jobs. Options: gcs-fuse (mount GCS bucket as filesystem,
gcs-fuse (mount GCS bucket as filesystem, unlimited size, best for large sequential I/O), unlimited size, best for large sequential I/O), gcs-copy (copy artifacts in/out via gsutil, simpler, no FUSE
gcs-copy (copy artifacts in/out via gsutil, simpler, no FUSE overhead), overhead), nfs (Filestore NFS mount, true POSIX, good random I/O, up to 100 TiB), in-memory (tmpfs, fastest but
nfs (Filestore NFS mount, true POSIX, good random I/O, up to 100 TiB), volatile, up to 32 GiB).'
in-memory (tmpfs, fastest but volatile, up to 32 GiB).'
gcpBucket: gcpBucket:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] [Experimental] GCS bucket name for build artifact storage. '[Orchestrator] [Experimental] GCS bucket name for build artifact storage. Used by gcs-fuse and gcs-copy storage
Used by gcs-fuse and gcs-copy storage types.' types.'
gcpFilestoreIp: gcpFilestoreIp:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] [Experimental] Filestore instance IP address for NFS storage type. '[Orchestrator] [Experimental] Filestore instance IP address for NFS storage type. Required when gcpStorageType is
Required when gcpStorageType is nfs.' nfs.'
gcpFilestoreShare: gcpFilestoreShare:
required: false required: false
default: '/share1' default: '/share1'
description: description:
'[Orchestrator] [Experimental] Filestore share name for NFS storage type. '[Orchestrator] [Experimental] Filestore share name for NFS storage type. Defaults to /share1 (the Filestore
Defaults to /share1 (the Filestore default).' default).'
gcpMachineType: gcpMachineType:
required: false required: false
default: 'e2-standard-4' default: 'e2-standard-4'
description: description: '[Orchestrator] [Experimental] Machine type for Cloud Run Jobs (e.g. e2-standard-4, e2-highmem-8).'
'[Orchestrator] [Experimental] Machine type for Cloud Run Jobs (e.g. e2-standard-4, e2-highmem-8).'
gcpDiskSizeGb: gcpDiskSizeGb:
required: false required: false
default: '100' default: '100'
description: description:
'[Orchestrator] [Experimental] Disk size in GB for Cloud Run Jobs in-memory volumes. '[Orchestrator] [Experimental] Disk size in GB for Cloud Run Jobs in-memory volumes. Only applies to in-memory
Only applies to in-memory storage type (max 32).' storage type (max 32).'
gcpServiceAccount: gcpServiceAccount:
required: false required: false
default: '' default: ''
description: description: '[Orchestrator] [Experimental] Google Cloud service account email for Cloud Run Jobs execution.'
'[Orchestrator] [Experimental] Google Cloud service account email for Cloud Run Jobs execution.'
gcpVpcConnector: gcpVpcConnector:
required: false required: false
default: '' default: ''
description: description: '[Orchestrator] [Experimental] VPC connector name for Cloud Run Jobs private networking.'
'[Orchestrator] [Experimental] VPC connector name for Cloud Run Jobs private networking.'
azureResourceGroup: azureResourceGroup:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] [Experimental] Azure resource group for Container Instances provider. '[Orchestrator] [Experimental] Azure resource group for Container Instances provider. Falls back to
Falls back to AZURE_RESOURCE_GROUP env var.' AZURE_RESOURCE_GROUP env var.'
azureLocation: azureLocation:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] [Experimental] Azure region for Container Instances (e.g. eastus, westeurope). '[Orchestrator] [Experimental] Azure region for Container Instances (e.g. eastus, westeurope). Defaults to the
Defaults to the region input if empty.' region input if empty.'
azureStorageType: azureStorageType:
required: false required: false
default: 'azure-files' default: 'azure-files'
description: description:
'[Orchestrator] [Experimental] Storage type for Azure Container Instances. Options: '[Orchestrator] [Experimental] Storage type for Azure Container Instances. Options: azure-files (SMB file share
azure-files (SMB file share mount, up to 100 TiB, premium throughput), mount, up to 100 TiB, premium throughput), blob-copy (copy artifacts in/out via az storage blob, no mount
blob-copy (copy artifacts in/out via az storage blob, no mount overhead), overhead), azure-files-nfs (NFS 4.1 file share mount, true POSIX, no SMB lock overhead), in-memory (emptyDir
azure-files-nfs (NFS 4.1 file share mount, true POSIX, no SMB lock overhead), tmpfs, fastest but volatile, size limited by container memory).'
in-memory (emptyDir tmpfs, fastest but volatile, size limited by container memory).'
azureStorageAccount: azureStorageAccount:
required: false required: false
default: '' default: ''
description: description:
'[Orchestrator] [Experimental] Azure Storage Account name. '[Orchestrator] [Experimental] Azure Storage Account name. Used by azure-files, azure-files-nfs, and blob-copy
Used by azure-files, azure-files-nfs, and blob-copy storage types.' storage types.'
azureFileShareName: azureFileShareName:
required: false required: false
default: 'unity-builds' default: 'unity-builds'
description: description:
'[Orchestrator] [Experimental] Azure File Share name within the storage account. '[Orchestrator] [Experimental] Azure File Share name within the storage account. Used by azure-files and
Used by azure-files and azure-files-nfs storage types. Supports up to 100 TiB per share.' azure-files-nfs storage types. Supports up to 100 TiB per share.'
azureBlobContainer: azureBlobContainer:
required: false required: false
default: 'unity-builds' default: 'unity-builds'
description: description: '[Orchestrator] [Experimental] Azure Blob container name for blob-copy storage type.'
'[Orchestrator] [Experimental] Azure Blob container name for blob-copy storage type.'
azureSubscriptionId: azureSubscriptionId:
required: false required: false
default: '' default: ''
description: description: '[Orchestrator] [Experimental] Azure subscription ID. Falls back to AZURE_SUBSCRIPTION_ID env var.'
'[Orchestrator] [Experimental] Azure subscription ID. Falls back to AZURE_SUBSCRIPTION_ID env var.'
azureCpu: azureCpu:
required: false required: false
default: '4' default: '4'
description: description: '[Orchestrator] [Experimental] CPU cores for Azure Container Instances (1-16).'
'[Orchestrator] [Experimental] CPU cores for Azure Container Instances (1-16).'
azureMemoryGb: azureMemoryGb:
required: false required: false
default: '16' default: '16'
description: description: '[Orchestrator] [Experimental] Memory in GB for Azure Container Instances (1-16).'
'[Orchestrator] [Experimental] Memory in GB for Azure Container Instances (1-16).'
azureDiskSizeGb: azureDiskSizeGb:
required: false required: false
default: '100' default: '100'
description: description:
'[Orchestrator] [Experimental] File share quota in GB for Azure Container Instances. '[Orchestrator] [Experimental] File share quota in GB for Azure Container Instances. Premium shares support up to
Premium shares support up to 102400 GB (100 TiB).' 102400 GB (100 TiB).'
azureSubnetId: azureSubnetId:
required: false required: false
default: '' default: ''
description: description: '[Orchestrator] [Experimental] Azure subnet resource ID for VNet-integrated Container Instances.'
'[Orchestrator] [Experimental] Azure subnet resource ID for VNet-integrated Container Instances.'
outputs: outputs:
volume: volume:
Generated Vendored
+853 -1
View File
@@ -361,6 +361,27 @@ class BuildParameters {
inputPullCommand: orchestrator_options_1.default.inputPullCommand, inputPullCommand: orchestrator_options_1.default.inputPullCommand,
pullInputList: orchestrator_options_1.default.pullInputList, pullInputList: orchestrator_options_1.default.pullInputList,
kubeStorageClass: orchestrator_options_1.default.kubeStorageClass, kubeStorageClass: orchestrator_options_1.default.kubeStorageClass,
gcpProject: input_1.default.gcpProject,
gcpRegion: input_1.default.gcpRegion,
gcpStorageType: input_1.default.gcpStorageType,
gcpBucket: input_1.default.gcpBucket,
gcpFilestoreIp: input_1.default.gcpFilestoreIp,
gcpFilestoreShare: input_1.default.gcpFilestoreShare,
gcpMachineType: input_1.default.gcpMachineType,
gcpDiskSizeGb: input_1.default.gcpDiskSizeGb,
gcpServiceAccount: input_1.default.gcpServiceAccount,
gcpVpcConnector: input_1.default.gcpVpcConnector,
azureResourceGroup: input_1.default.azureResourceGroup,
azureLocation: input_1.default.azureLocation,
azureStorageType: input_1.default.azureStorageType,
azureStorageAccount: input_1.default.azureStorageAccount,
azureBlobContainer: input_1.default.azureBlobContainer,
azureFileShareName: input_1.default.azureFileShareName,
azureSubscriptionId: input_1.default.azureSubscriptionId,
azureCpu: input_1.default.azureCpu,
azureMemoryGb: input_1.default.azureMemoryGb,
azureDiskSizeGb: input_1.default.azureDiskSizeGb,
azureSubnetId: input_1.default.azureSubnetId,
cacheKey: orchestrator_options_1.default.cacheKey, cacheKey: orchestrator_options_1.default.cacheKey,
maxRetainedWorkspaces: Number.parseInt(orchestrator_options_1.default.maxRetainedWorkspaces), maxRetainedWorkspaces: Number.parseInt(orchestrator_options_1.default.maxRetainedWorkspaces),
useLargePackages: orchestrator_options_1.default.useLargePackages, useLargePackages: orchestrator_options_1.default.useLargePackages,
@@ -1826,6 +1847,71 @@ class Input {
static get skipActivation() { static get skipActivation() {
return Input.getInput('skipActivation')?.toLowerCase() ?? 'false'; return Input.getInput('skipActivation')?.toLowerCase() ?? 'false';
} }
// GCP Cloud Run (Experimental)
static get gcpProject() {
return Input.getInput('gcpProject') ?? '';
}
static get gcpRegion() {
return Input.getInput('gcpRegion') ?? '';
}
static get gcpStorageType() {
return Input.getInput('gcpStorageType') ?? 'gcs-fuse';
}
static get gcpBucket() {
return Input.getInput('gcpBucket') ?? '';
}
static get gcpFilestoreIp() {
return Input.getInput('gcpFilestoreIp') ?? '';
}
static get gcpFilestoreShare() {
return Input.getInput('gcpFilestoreShare') ?? '/share1';
}
static get gcpMachineType() {
return Input.getInput('gcpMachineType') ?? 'e2-standard-4';
}
static get gcpDiskSizeGb() {
return Input.getInput('gcpDiskSizeGb') ?? '100';
}
static get gcpServiceAccount() {
return Input.getInput('gcpServiceAccount') ?? '';
}
static get gcpVpcConnector() {
return Input.getInput('gcpVpcConnector') ?? '';
}
// Azure Container Instances (Experimental)
static get azureResourceGroup() {
return Input.getInput('azureResourceGroup') ?? '';
}
static get azureLocation() {
return Input.getInput('azureLocation') ?? '';
}
static get azureStorageType() {
return Input.getInput('azureStorageType') ?? 'azure-files';
}
static get azureStorageAccount() {
return Input.getInput('azureStorageAccount') ?? '';
}
static get azureBlobContainer() {
return Input.getInput('azureBlobContainer') ?? 'unity-builds';
}
static get azureFileShareName() {
return Input.getInput('azureFileShareName') ?? 'unity-builds';
}
static get azureSubscriptionId() {
return Input.getInput('azureSubscriptionId') ?? '';
}
static get azureCpu() {
return Input.getInput('azureCpu') ?? '4';
}
static get azureMemoryGb() {
return Input.getInput('azureMemoryGb') ?? '16';
}
static get azureDiskSizeGb() {
return Input.getInput('azureDiskSizeGb') ?? '100';
}
static get azureSubnetId() {
return Input.getInput('azureSubnetId') ?? '';
}
static ToEnvVarFormat(input) { static ToEnvVarFormat(input) {
if (input.toUpperCase() === input) { if (input.toUpperCase() === input) {
return input; return input;
@@ -2501,6 +2587,8 @@ const core = __importStar(__nccwpck_require__(42186));
const test_1 = __importDefault(__nccwpck_require__(6389)); const test_1 = __importDefault(__nccwpck_require__(6389));
const local_1 = __importDefault(__nccwpck_require__(48195)); const local_1 = __importDefault(__nccwpck_require__(48195));
const docker_1 = __importDefault(__nccwpck_require__(91739)); const docker_1 = __importDefault(__nccwpck_require__(91739));
const gcp_cloud_run_1 = __importDefault(__nccwpck_require__(84818));
const azure_aci_1 = __importDefault(__nccwpck_require__(94129));
const provider_loader_1 = __importDefault(__nccwpck_require__(50822)); const provider_loader_1 = __importDefault(__nccwpck_require__(50822));
const github_1 = __importDefault(__nccwpck_require__(83654)); const github_1 = __importDefault(__nccwpck_require__(83654));
const shared_workspace_locking_1 = __importDefault(__nccwpck_require__(54222)); const shared_workspace_locking_1 = __importDefault(__nccwpck_require__(54222));
@@ -2619,6 +2707,14 @@ class Orchestrator {
case 'local': case 'local':
Orchestrator.Provider = new local_1.default(); Orchestrator.Provider = new local_1.default();
break; break;
case 'gcp-cloud-run':
orchestrator_logger_1.default.log('⚠ EXPERIMENTAL: GCP Cloud Run Jobs provider');
Orchestrator.Provider = new gcp_cloud_run_1.default(Orchestrator.buildParameters);
break;
case 'azure-aci':
orchestrator_logger_1.default.log('⚠ EXPERIMENTAL: Azure Container Instances provider');
Orchestrator.Provider = new azure_aci_1.default(Orchestrator.buildParameters);
break;
default: default:
// Try to load provider using the dynamic loader for unknown providers // Try to load provider using the dynamic loader for unknown providers
try { try {
@@ -4749,6 +4845,417 @@ class TaskService {
exports.TaskService = TaskService; exports.TaskService = TaskService;
/***/ }),
/***/ 94129:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
/**
* Azure Container Instances (ACI) Provider (Experimental)
*
* Executes Unity builds as Azure Container Instances with configurable storage backends.
*
* Storage types:
* - azure-files: SMB file share mount via Azure Files. Up to 100 TiB per share,
* premium throughput. Default.
* Requires: azureStorageAccount, azureFileShareName
* - blob-copy: Copy artifacts in/out of Azure Blob Storage before/after the build.
* No mount overhead, simpler.
* Requires: azureStorageAccount, azureBlobContainer
* - azure-files-nfs: NFS 4.1 file share mount. True POSIX semantics, no SMB lock overhead,
* better for Unity Library caching (many small random reads).
* Requires: azureStorageAccount, azureFileShareName, Premium FileStorage,
* VNet integration (azureSubnetId)
* - in-memory: emptyDir volume (tmpfs). Fastest I/O but volatile, size limited by
* container memory allocation.
*
* Prerequisites:
* - Azure CLI authenticated (az login or service principal)
* - A resource group for build resources
* - Contributor role on the resource group
*
* @experimental This provider is experimental. APIs and behavior may change.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
const orchestrator_system_1 = __nccwpck_require__(9744);
const __1 = __nccwpck_require__(41359);
const resource_tracking_1 = __importDefault(__nccwpck_require__(42604));
class AzureAciProvider {
constructor(buildParameters) {
this.buildParameters = buildParameters;
this.resourceGroup = buildParameters.azureResourceGroup || process.env.AZURE_RESOURCE_GROUP || '';
this.location = buildParameters.azureLocation || __1.Input.region || 'eastus';
this.storageType = (buildParameters.azureStorageType || 'azure-files');
this.storageAccount = buildParameters.azureStorageAccount || process.env.AZURE_STORAGE_ACCOUNT || '';
this.blobContainer = buildParameters.azureBlobContainer || 'unity-builds';
this.fileShareName = buildParameters.azureFileShareName || 'unity-builds';
this.subscriptionId = buildParameters.azureSubscriptionId || process.env.AZURE_SUBSCRIPTION_ID || '';
this.cpu = Number.parseInt(buildParameters.azureCpu || '4', 10);
this.memoryGb = Number.parseInt(buildParameters.azureMemoryGb || '16', 10);
this.diskSizeGb = Number.parseInt(buildParameters.azureDiskSizeGb || '100', 10);
this.subnetId = buildParameters.azureSubnetId || '';
orchestrator_logger_1.default.log('[Azure ACI] Provider initialized (EXPERIMENTAL)');
orchestrator_logger_1.default.log(`[Azure ACI] Resource Group: ${this.resourceGroup || '(not set)'}`);
orchestrator_logger_1.default.log(`[Azure ACI] Location: ${this.location}`);
orchestrator_logger_1.default.log(`[Azure ACI] Storage: ${this.storageType}`);
orchestrator_logger_1.default.log(`[Azure ACI] Resources: ${this.cpu} CPU, ${this.memoryGb}GB RAM`);
this.validateStorageConfig();
}
validateStorageConfig() {
switch (this.storageType) {
case 'azure-files':
if (!this.storageAccount) {
orchestrator_logger_1.default.logWarning('[Azure ACI] Storage type "azure-files" requires azureStorageAccount to be set.');
}
else {
orchestrator_logger_1.default.log(`[Azure ACI] File Share: ${this.storageAccount}/${this.fileShareName} (SMB)`);
}
break;
case 'azure-files-nfs':
if (!this.storageAccount) {
orchestrator_logger_1.default.logWarning('[Azure ACI] Storage type "azure-files-nfs" requires azureStorageAccount (Premium FileStorage).');
}
if (!this.subnetId) {
orchestrator_logger_1.default.logWarning('[Azure ACI] NFS file shares require VNet integration. Set azureSubnetId.');
}
else {
orchestrator_logger_1.default.log(`[Azure ACI] File Share: ${this.storageAccount}/${this.fileShareName} (NFS 4.1)`);
}
break;
case 'blob-copy':
if (!this.storageAccount) {
orchestrator_logger_1.default.logWarning('[Azure ACI] Storage type "blob-copy" requires azureStorageAccount to be set.');
}
else {
orchestrator_logger_1.default.log(`[Azure ACI] Blob container: ${this.storageAccount}/${this.blobContainer}`);
}
break;
case 'in-memory':
orchestrator_logger_1.default.log(`[Azure ACI] In-memory volume (emptyDir): limited by ${this.memoryGb}GB container memory`);
break;
default:
orchestrator_logger_1.default.logWarning(`[Azure ACI] Unknown storage type '${this.storageType}'. Valid: azure-files, blob-copy, azure-files-nfs, in-memory`);
}
if (!this.resourceGroup) {
orchestrator_logger_1.default.logWarning('[Azure ACI] No resource group specified. Set azureResourceGroup input or AZURE_RESOURCE_GROUP env var.');
}
}
async setupWorkflow(buildGuid, buildParameters, branchName, defaultSecretsArray) {
orchestrator_logger_1.default.log(`[Azure ACI] Setting up workflow for build ${buildGuid}`);
resource_tracking_1.default.logAllocationSummary('azure-aci setup');
// Verify Azure CLI is available
try {
await orchestrator_system_1.OrchestratorSystem.Run('az version --output json', false, true);
orchestrator_logger_1.default.log('[Azure ACI] Azure CLI detected');
}
catch {
throw new Error('[Azure ACI] Azure CLI not found. Install Azure CLI: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli');
}
if (this.subscriptionId) {
await orchestrator_system_1.OrchestratorSystem.Run(`az account set --subscription="${this.subscriptionId}"`);
}
// Ensure resource group exists
if (this.resourceGroup) {
try {
await orchestrator_system_1.OrchestratorSystem.Run(`az group show --name "${this.resourceGroup}" --output json`, false, true);
orchestrator_logger_1.default.log(`[Azure ACI] Resource group ${this.resourceGroup} exists`);
}
catch {
orchestrator_logger_1.default.log(`[Azure ACI] Creating resource group ${this.resourceGroup}`);
await orchestrator_system_1.OrchestratorSystem.Run(`az group create --name "${this.resourceGroup}" --location "${this.location}"`);
}
}
// Storage-specific setup
switch (this.storageType) {
case 'azure-files':
await this.setupStorageAccount('Standard_LRS', 'StorageV2');
await this.setupFileShare();
break;
case 'azure-files-nfs':
await this.setupStorageAccount('Premium_LRS', 'FileStorage');
await this.setupNfsFileShare();
break;
case 'blob-copy':
await this.setupStorageAccount('Standard_LRS', 'StorageV2');
await this.setupBlobContainer();
break;
case 'in-memory':
// No storage setup needed
break;
}
}
async setupStorageAccount(sku, kind) {
if (!this.storageAccount || !this.resourceGroup)
return;
try {
await orchestrator_system_1.OrchestratorSystem.Run(`az storage account show --name "${this.storageAccount}" --resource-group "${this.resourceGroup}" --output json`, false, true);
orchestrator_logger_1.default.log(`[Azure ACI] Storage account ${this.storageAccount} exists`);
}
catch {
orchestrator_logger_1.default.log(`[Azure ACI] Creating storage account ${this.storageAccount} (${sku}, ${kind})`);
await orchestrator_system_1.OrchestratorSystem.Run(`az storage account create --name "${this.storageAccount}" --resource-group "${this.resourceGroup}" --location "${this.location}" --sku ${sku} --kind ${kind}`);
}
}
async setupFileShare() {
if (!this.storageAccount || !this.resourceGroup)
return;
try {
await orchestrator_system_1.OrchestratorSystem.Run(`az storage share-rm show --storage-account "${this.storageAccount}" --name "${this.fileShareName}" --resource-group "${this.resourceGroup}" --output json`, false, true);
}
catch {
orchestrator_logger_1.default.log(`[Azure ACI] Creating file share ${this.fileShareName} (${this.diskSizeGb}GB)`);
await orchestrator_system_1.OrchestratorSystem.Run(`az storage share-rm create --storage-account "${this.storageAccount}" --name "${this.fileShareName}" --resource-group "${this.resourceGroup}" --quota ${this.diskSizeGb}`);
}
}
async setupNfsFileShare() {
if (!this.storageAccount || !this.resourceGroup)
return;
try {
await orchestrator_system_1.OrchestratorSystem.Run(`az storage share-rm show --storage-account "${this.storageAccount}" --name "${this.fileShareName}" --resource-group "${this.resourceGroup}" --output json`, false, true);
}
catch {
orchestrator_logger_1.default.log(`[Azure ACI] Creating NFS file share ${this.fileShareName} (${this.diskSizeGb}GB)`);
await orchestrator_system_1.OrchestratorSystem.Run(`az storage share-rm create --storage-account "${this.storageAccount}" --name "${this.fileShareName}" --resource-group "${this.resourceGroup}" --quota ${this.diskSizeGb} --enabled-protocols NFS`);
}
}
async setupBlobContainer() {
if (!this.storageAccount || !this.resourceGroup)
return;
try {
await orchestrator_system_1.OrchestratorSystem.Run(`az storage container show --name "${this.blobContainer}" --account-name "${this.storageAccount}" --output json`, false, true);
}
catch {
orchestrator_logger_1.default.log(`[Azure ACI] Creating blob container ${this.blobContainer}`);
await orchestrator_system_1.OrchestratorSystem.Run(`az storage container create --name "${this.blobContainer}" --account-name "${this.storageAccount}"`);
}
}
async getStorageKey() {
if (!this.storageAccount || !this.resourceGroup)
return '';
try {
const keyJson = await orchestrator_system_1.OrchestratorSystem.Run(`az storage account keys list --account-name "${this.storageAccount}" --resource-group "${this.resourceGroup}" --output json`, false, true);
const keys = JSON.parse(keyJson);
return keys[0]?.value || '';
}
catch (error) {
orchestrator_logger_1.default.logWarning(`[Azure ACI] Could not get storage key: ${error.message}`);
return '';
}
}
async buildVolumeFlags(mountdir) {
switch (this.storageType) {
case 'azure-files': {
const storageKey = await this.getStorageKey();
if (!storageKey)
return '';
return [
`--azure-file-volume-account-name "${this.storageAccount}"`,
`--azure-file-volume-account-key "${storageKey}"`,
`--azure-file-volume-share-name "${this.fileShareName}"`,
`--azure-file-volume-mount-path "${mountdir}"`,
].join(' ');
}
case 'azure-files-nfs': {
// ACI NFS mount uses a YAML deployment template; for CLI we use the same
// azure-file-volume flags but the share must be NFS-enabled and
// the container must be in a VNet
const storageKey = await this.getStorageKey();
if (!storageKey)
return '';
return [
`--azure-file-volume-account-name "${this.storageAccount}"`,
`--azure-file-volume-account-key "${storageKey}"`,
`--azure-file-volume-share-name "${this.fileShareName}"`,
`--azure-file-volume-mount-path "${mountdir}"`,
].join(' ');
}
case 'in-memory':
// ACI emptyDir volumes require YAML deployment; for simplicity we skip
// the volume mount and let the container use its own filesystem
orchestrator_logger_1.default.log('[Azure ACI] In-memory mode: using container filesystem (no persistent mount)');
return '';
case 'blob-copy':
// No volume mount — artifacts are copied in/out via az storage blob commands
return '';
default:
return '';
}
}
async runTaskInWorkflow(buildGuid, image, commands, mountdir, workingdir, environment, secrets) {
orchestrator_logger_1.default.log(`[Azure ACI] Running task for build ${buildGuid}`);
resource_tracking_1.default.logAllocationSummary('azure-aci task');
const containerName = `unity-build-${buildGuid}`
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.slice(0, 63);
// Build environment variable flags
const allEnvVars = [
...environment.map((env) => `${env.name}=${env.value}`),
...secrets.map((s) => `${s.EnvironmentVariable}=${s.ParameterValue}`),
];
const envFlag = allEnvVars.length > 0 ? `--environment-variables ${allEnvVars.map((e) => `"${e}"`).join(' ')}` : '';
// Build volume flags based on storage type
const volumeFlags = await this.buildVolumeFlags(mountdir);
const subnetFlag = this.subnetId ? `--subnet "${this.subnetId}"` : '';
// For blob-copy, wrap the user command with copy-in/copy-out steps
let effectiveCommands = commands;
if (this.storageType === 'blob-copy' && this.storageAccount && commands) {
effectiveCommands = [
`az storage blob download-batch --destination "${mountdir}" --source "${this.blobContainer}" --account-name "${this.storageAccount}" 2>/dev/null || true`,
commands,
`az storage blob upload-batch --source "${mountdir}" --destination "${this.blobContainer}" --account-name "${this.storageAccount}" --overwrite`,
].join(' && ');
}
const commandFlag = effectiveCommands
? `--command-line "/bin/sh -c '${effectiveCommands.replace(/'/g, "'\\''")}'"`
: '';
const createCmd = [
'az container create',
`--resource-group "${this.resourceGroup}"`,
`--name "${containerName}"`,
`--image "${image}"`,
`--location "${this.location}"`,
`--cpu ${this.cpu}`,
`--memory ${this.memoryGb}`,
'--restart-policy Never',
'--os-type Linux',
volumeFlags,
envFlag,
subnetFlag,
commandFlag,
'--output json',
]
.filter(Boolean)
.join(' ');
try {
await orchestrator_system_1.OrchestratorSystem.Run(createCmd);
orchestrator_logger_1.default.log(`[Azure ACI] Container ${containerName} created (storage: ${this.storageType}), waiting for completion...`);
}
catch (error) {
throw new Error(`[Azure ACI] Failed to create container: ${error.message}`);
}
const output = await this.waitForContainerCompletion(containerName);
return output;
}
async waitForContainerCompletion(containerName) {
const maxWaitMs = 24 * 60 * 60 * 1000;
const pollIntervalMs = 15000;
const startTime = Date.now();
let lastLogLength = 0;
while (Date.now() - startTime < maxWaitMs) {
try {
const stateJson = await orchestrator_system_1.OrchestratorSystem.Run(`az container show --resource-group "${this.resourceGroup}" --name "${containerName}" --output json`, false, true);
const state = JSON.parse(stateJson);
const containerState = state.containers?.[0]?.instanceView?.currentState?.state || state.instanceView?.state || 'Unknown';
const provisioningState = state.provisioningState || 'Unknown';
// Stream logs incrementally
try {
const logs = await orchestrator_system_1.OrchestratorSystem.Run(`az container logs --resource-group "${this.resourceGroup}" --name "${containerName}"`, false, true);
if (logs && logs.length > lastLogLength) {
const newLogs = logs.slice(lastLogLength);
for (const line of newLogs.split('\n')) {
if (line.trim()) {
orchestrator_logger_1.default.log(`[Build] ${line}`);
}
}
lastLogLength = logs.length;
}
}
catch {
// Logs may not be available yet
}
if (containerState === 'Terminated' || provisioningState === 'Succeeded') {
const exitCode = state.containers?.[0]?.instanceView?.currentState?.exitCode;
if (exitCode !== undefined && exitCode !== 0) {
throw new Error(`[Azure ACI] Container exited with code ${exitCode}`);
}
orchestrator_logger_1.default.log('[Azure ACI] Container completed successfully');
try {
return await orchestrator_system_1.OrchestratorSystem.Run(`az container logs --resource-group "${this.resourceGroup}" --name "${containerName}"`, false, true);
}
catch {
return '';
}
}
if (provisioningState === 'Failed') {
const detail = state.containers?.[0]?.instanceView?.currentState?.detailStatus ||
state.containers?.[0]?.instanceView?.events?.map((e) => e.message).join('; ') ||
'Unknown error';
throw new Error(`[Azure ACI] Container provisioning failed: ${detail}`);
}
}
catch (error) {
if (error.message?.includes('Container provisioning failed') || error.message?.includes('exited with code')) {
throw error;
}
orchestrator_logger_1.default.logWarning(`[Azure ACI] Polling error: ${error.message}`);
}
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
}
throw new Error('[Azure ACI] Container execution timed out after 24 hours');
}
async cleanupWorkflow(buildParameters, branchName, defaultSecretsArray) {
orchestrator_logger_1.default.log('[Azure ACI] Cleaning up workflow');
}
async garbageCollect(filter, previewOnly, olderThan, fullCache, baseDependencies) {
orchestrator_logger_1.default.log('[Azure ACI] Garbage collecting old container groups');
try {
const containersJson = await orchestrator_system_1.OrchestratorSystem.Run(`az container list --resource-group "${this.resourceGroup}" --output json`, false, true);
const containers = JSON.parse(containersJson || '[]');
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - Number(olderThan));
let deletedCount = 0;
for (const container of containers) {
const name = container.name || '';
if (!name.startsWith('unity-build-'))
continue;
const createdAt = new Date(container.tags?.createdAt || container.properties?.provisioningState || 0);
const state = container.containers?.[0]?.instanceView?.currentState?.state || '';
if (state === 'Terminated' || createdAt < cutoffDate) {
if (previewOnly) {
orchestrator_logger_1.default.log(`[Azure ACI] Would delete: ${name}`);
}
else {
await orchestrator_system_1.OrchestratorSystem.Run(`az container delete --resource-group "${this.resourceGroup}" --name "${name}" --yes`);
deletedCount++;
}
}
}
return `Garbage collected ${deletedCount} Azure container instances`;
}
catch (error) {
orchestrator_logger_1.default.logWarning(`[Azure ACI] Garbage collection failed: ${error.message}`);
return '';
}
}
async listResources() {
try {
const containersJson = await orchestrator_system_1.OrchestratorSystem.Run(`az container list --resource-group "${this.resourceGroup}" --output json`, false, true);
const containers = JSON.parse(containersJson || '[]');
return containers
.filter((c) => (c.name || '').startsWith('unity-build-'))
.map((c) => ({ Name: c.name || '' }));
}
catch {
return [];
}
}
listWorkflow() {
throw new Error('[Azure ACI] listWorkflow not implemented for this experimental provider');
}
async watchWorkflow() {
throw new Error('[Azure ACI] watchWorkflow not implemented for this experimental provider');
}
}
exports["default"] = AzureAciProvider;
/***/ }), /***/ }),
/***/ 91739: /***/ 91739:
@@ -4926,6 +5433,349 @@ find ${sharedFolder} -maxdepth 1 -type f -name "test-*" -exec cp -a {} /github/w
exports["default"] = LocalDockerOrchestrator; exports["default"] = LocalDockerOrchestrator;
/***/ }),
/***/ 84818:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
/**
* Google Cloud Run Jobs Provider (Experimental)
*
* Executes Unity builds as Cloud Run Jobs with configurable storage backends.
*
* Storage types:
* - gcs-fuse: Mount a GCS bucket as a POSIX filesystem via GCS FUSE sidecar.
* Unlimited size, best for large sequential reads/writes.
* Requires: gcpBucket
* - gcs-copy: Copy artifacts in/out of GCS before/after the build via gsutil.
* No mount overhead, simpler, works everywhere.
* Requires: gcpBucket
* - nfs: Mount a Filestore NFS share. True POSIX semantics, good random I/O,
* up to 100 TiB. Best for Library caching (many small random reads).
* Requires: gcpFilestoreIp, gcpFilestoreShare
* - in-memory: tmpfs volume (emptyDir). Fastest I/O but volatile and limited to 32 GiB.
* Good for scratch/temp space during builds.
*
* Prerequisites:
* - Google Cloud SDK authenticated (GOOGLE_APPLICATION_CREDENTIALS or gcloud auth)
* - Cloud Run Jobs API enabled
* - Service account with roles: Cloud Run Admin, Storage Admin, Logs Viewer
*
* @experimental This provider is experimental. APIs and behavior may change.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
const orchestrator_system_1 = __nccwpck_require__(9744);
const __1 = __nccwpck_require__(41359);
const resource_tracking_1 = __importDefault(__nccwpck_require__(42604));
class GcpCloudRunProvider {
constructor(buildParameters) {
this.buildParameters = buildParameters;
this.project = buildParameters.gcpProject || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || '';
this.region = buildParameters.gcpRegion || __1.Input.region || 'us-central1';
this.storageType = (buildParameters.gcpStorageType || 'gcs-fuse');
this.bucket = buildParameters.gcpBucket || '';
this.filestoreIp = buildParameters.gcpFilestoreIp || '';
this.filestoreShare = buildParameters.gcpFilestoreShare || '/share1';
this.machineType = buildParameters.gcpMachineType || 'e2-standard-4';
this.diskSizeGb = Number.parseInt(buildParameters.gcpDiskSizeGb || '100', 10);
this.serviceAccount = buildParameters.gcpServiceAccount || '';
this.vpcConnector = buildParameters.gcpVpcConnector || '';
orchestrator_logger_1.default.log('[GCP Cloud Run] Provider initialized (EXPERIMENTAL)');
orchestrator_logger_1.default.log(`[GCP Cloud Run] Project: ${this.project || '(auto-detect)'}`);
orchestrator_logger_1.default.log(`[GCP Cloud Run] Region: ${this.region}`);
orchestrator_logger_1.default.log(`[GCP Cloud Run] Storage: ${this.storageType}`);
this.validateStorageConfig();
}
validateStorageConfig() {
switch (this.storageType) {
case 'gcs-fuse':
case 'gcs-copy':
if (!this.bucket) {
orchestrator_logger_1.default.logWarning(`[GCP Cloud Run] Storage type '${this.storageType}' requires gcpBucket to be set.`);
}
else {
orchestrator_logger_1.default.log(`[GCP Cloud Run] Bucket: gs://${this.bucket}`);
}
break;
case 'nfs':
if (!this.filestoreIp) {
orchestrator_logger_1.default.logWarning('[GCP Cloud Run] Storage type "nfs" requires gcpFilestoreIp to be set.');
}
else {
orchestrator_logger_1.default.log(`[GCP Cloud Run] Filestore: ${this.filestoreIp}:${this.filestoreShare}`);
}
if (!this.vpcConnector) {
orchestrator_logger_1.default.logWarning('[GCP Cloud Run] NFS storage usually requires gcpVpcConnector for private network access to Filestore.');
}
break;
case 'in-memory':
orchestrator_logger_1.default.log(`[GCP Cloud Run] In-memory volume: ${Math.min(this.diskSizeGb, 32)} GiB (max 32)`);
break;
default:
orchestrator_logger_1.default.logWarning(`[GCP Cloud Run] Unknown storage type '${this.storageType}'. Valid: gcs-fuse, gcs-copy, nfs, in-memory`);
}
if (!this.project) {
orchestrator_logger_1.default.logWarning('[GCP Cloud Run] No project specified. Set gcpProject input or GOOGLE_CLOUD_PROJECT env var.');
}
}
async setupWorkflow(buildGuid, buildParameters, branchName, defaultSecretsArray) {
orchestrator_logger_1.default.log(`[GCP Cloud Run] Setting up workflow for build ${buildGuid}`);
resource_tracking_1.default.logAllocationSummary('gcp-cloud-run setup');
// Verify gcloud CLI is available
try {
await orchestrator_system_1.OrchestratorSystem.Run('gcloud --version', false, true);
orchestrator_logger_1.default.log('[GCP Cloud Run] gcloud CLI detected');
}
catch {
throw new Error('[GCP Cloud Run] gcloud CLI not found. Install Google Cloud SDK: https://cloud.google.com/sdk/docs/install');
}
// Verify Cloud Run Jobs API is enabled
try {
const projectFlag = this.project ? `--project=${this.project}` : '';
await orchestrator_system_1.OrchestratorSystem.Run(`gcloud services list --enabled --filter="name:run.googleapis.com" ${projectFlag} --format="value(name)"`, false, true);
}
catch {
orchestrator_logger_1.default.logWarning('[GCP Cloud Run] Could not verify Cloud Run API status. Ensure run.googleapis.com is enabled.');
}
// Storage-specific setup
if ((this.storageType === 'gcs-fuse' || this.storageType === 'gcs-copy') && this.bucket) {
await this.ensureBucketExists();
}
}
async ensureBucketExists() {
try {
await orchestrator_system_1.OrchestratorSystem.Run(`gcloud storage buckets describe gs://${this.bucket} --format="value(name)"`, false, true);
orchestrator_logger_1.default.log(`[GCP Cloud Run] Bucket gs://${this.bucket} exists`);
}
catch {
orchestrator_logger_1.default.log(`[GCP Cloud Run] Creating bucket gs://${this.bucket}`);
const projectFlag = this.project ? `--project=${this.project}` : '';
await orchestrator_system_1.OrchestratorSystem.Run(`gcloud storage buckets create gs://${this.bucket} --location=${this.region} ${projectFlag}`);
}
}
buildVolumeFlags(mountdir) {
switch (this.storageType) {
case 'gcs-fuse':
if (!this.bucket)
return { volumeFlags: '', mountFlags: '' };
return {
volumeFlags: `--add-volume=name=gcs-fuse,type=cloud-storage,bucket=${this.bucket}`,
mountFlags: `--add-volume-mount=volume=gcs-fuse,mount-path=${mountdir}`,
};
case 'nfs':
if (!this.filestoreIp)
return { volumeFlags: '', mountFlags: '' };
return {
volumeFlags: `--add-volume=name=nfs-vol,type=nfs,location=${this.filestoreIp}:${this.filestoreShare}`,
mountFlags: `--add-volume-mount=volume=nfs-vol,mount-path=${mountdir}`,
};
case 'in-memory': {
const sizeGib = Math.min(this.diskSizeGb, 32);
return {
volumeFlags: `--add-volume=name=tmpfs-vol,type=in-memory,size-limit=${sizeGib}Gi`,
mountFlags: `--add-volume-mount=volume=tmpfs-vol,mount-path=${mountdir}`,
};
}
case 'gcs-copy':
// No volume mount — artifacts are copied in/out via gsutil commands
return { volumeFlags: '', mountFlags: '' };
default:
return { volumeFlags: '', mountFlags: '' };
}
}
async copyArtifactsIn(mountdir) {
if (this.storageType !== 'gcs-copy' || !this.bucket)
return;
orchestrator_logger_1.default.log(`[GCP Cloud Run] Copying artifacts from gs://${this.bucket} to ${mountdir}`);
try {
await orchestrator_system_1.OrchestratorSystem.Run(`gcloud storage cp -r "gs://${this.bucket}/*" "${mountdir}/" || true`, false, true);
}
catch {
orchestrator_logger_1.default.log('[GCP Cloud Run] No existing artifacts to restore (bucket may be empty)');
}
}
async copyArtifactsOut(mountdir) {
if (this.storageType !== 'gcs-copy' || !this.bucket)
return;
orchestrator_logger_1.default.log(`[GCP Cloud Run] Uploading artifacts from ${mountdir} to gs://${this.bucket}`);
await orchestrator_system_1.OrchestratorSystem.Run(`gcloud storage cp -r "${mountdir}/*" "gs://${this.bucket}/"`, false, true);
}
async runTaskInWorkflow(buildGuid, image, commands, mountdir, workingdir, environment, secrets) {
orchestrator_logger_1.default.log(`[GCP Cloud Run] Running task for build ${buildGuid}`);
resource_tracking_1.default.logAllocationSummary('gcp-cloud-run task');
const jobName = `unity-build-${buildGuid}`
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.slice(0, 63);
const projectFlag = this.project ? `--project=${this.project}` : '';
// Build environment variable flags
const envFlags = environment
.map((env) => `${env.name}=${env.value}`)
.concat(secrets.map((s) => `${s.EnvironmentVariable}=${s.ParameterValue}`));
const envString = envFlags.length > 0 ? `--set-env-vars="${envFlags.join(',')}"` : '';
// Build storage volume flags
const { volumeFlags, mountFlags } = this.buildVolumeFlags(mountdir);
// For gcs-copy, wrap the user command with copy-in/copy-out steps
let effectiveCommands = commands;
if (this.storageType === 'gcs-copy' && this.bucket && commands) {
effectiveCommands = [
`gcloud storage cp -r "gs://${this.bucket}/*" "${mountdir}/" 2>/dev/null || true`,
commands,
`gcloud storage cp -r "${mountdir}/*" "gs://${this.bucket}/"`,
].join(' && ');
}
const saFlag = this.serviceAccount ? `--service-account=${this.serviceAccount}` : '';
const vpcFlag = this.vpcConnector ? `--vpc-connector=${this.vpcConnector}` : '';
// Create the Cloud Run Job
const createCmd = [
'gcloud run jobs create',
jobName,
`--image=${image}`,
`--region=${this.region}`,
'--task-timeout=86400s',
'--max-retries=0',
'--cpu=4',
'--memory=16Gi',
volumeFlags,
mountFlags,
envString,
saFlag,
vpcFlag,
projectFlag,
'--format=json',
'--quiet',
]
.filter(Boolean)
.join(' ');
try {
await orchestrator_system_1.OrchestratorSystem.Run(createCmd);
orchestrator_logger_1.default.log(`[GCP Cloud Run] Job ${jobName} created`);
}
catch (error) {
if (error.message?.includes('already exists')) {
orchestrator_logger_1.default.log(`[GCP Cloud Run] Job ${jobName} already exists, updating...`);
const updateCmd = createCmd.replace('jobs create', 'jobs update');
await orchestrator_system_1.OrchestratorSystem.Run(updateCmd);
}
else {
throw error;
}
}
// Override the command if provided
if (effectiveCommands) {
const updateCmd = [
'gcloud run jobs update',
jobName,
`--region=${this.region}`,
'--command="/bin/sh"',
`--args="-c,${effectiveCommands}"`,
projectFlag,
'--quiet',
]
.filter(Boolean)
.join(' ');
await orchestrator_system_1.OrchestratorSystem.Run(updateCmd);
}
// Execute the job
orchestrator_logger_1.default.log(`[GCP Cloud Run] Executing job ${jobName} (storage: ${this.storageType})...`);
const executeCmd = [
'gcloud run jobs execute',
jobName,
`--region=${this.region}`,
projectFlag,
'--wait',
'--format=json',
'--quiet',
]
.filter(Boolean)
.join(' ');
let output = '';
try {
output = await orchestrator_system_1.OrchestratorSystem.Run(executeCmd);
orchestrator_logger_1.default.log('[GCP Cloud Run] Job execution completed');
}
catch (error) {
await this.streamJobLogs(jobName);
throw new Error(`[GCP Cloud Run] Job execution failed: ${error.message}`);
}
await this.streamJobLogs(jobName);
return output;
}
async streamJobLogs(jobName) {
const projectFlag = this.project ? `--project=${this.project}` : '';
try {
const logs = await orchestrator_system_1.OrchestratorSystem.Run(`gcloud logging read "resource.type=cloud_run_job AND resource.labels.job_name=${jobName}" ${projectFlag} --limit=1000 --format="value(textPayload)" --order=asc`, false, true);
if (logs) {
for (const line of logs.split('\n')) {
if (line.trim()) {
orchestrator_logger_1.default.log(`[Build] ${line}`);
}
}
}
}
catch {
orchestrator_logger_1.default.logWarning('[GCP Cloud Run] Could not retrieve job logs');
}
}
async cleanupWorkflow(buildParameters, branchName, defaultSecretsArray) {
orchestrator_logger_1.default.log('[GCP Cloud Run] Cleaning up workflow');
}
async garbageCollect(filter, previewOnly, olderThan, fullCache, baseDependencies) {
orchestrator_logger_1.default.log('[GCP Cloud Run] Garbage collecting old jobs');
const projectFlag = this.project ? `--project=${this.project}` : '';
try {
const jobsJson = await orchestrator_system_1.OrchestratorSystem.Run(`gcloud run jobs list --region=${this.region} ${projectFlag} --filter="metadata.name~unity-build-" --format="json(metadata.name,metadata.creationTimestamp)"`, false, true);
const jobs = JSON.parse(jobsJson || '[]');
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - Number(olderThan));
let deletedCount = 0;
for (const job of jobs) {
const createdAt = new Date(job.metadata?.creationTimestamp || 0);
if (createdAt < cutoffDate) {
const name = job.metadata?.name;
if (previewOnly) {
orchestrator_logger_1.default.log(`[GCP Cloud Run] Would delete: ${name}`);
}
else {
await orchestrator_system_1.OrchestratorSystem.Run(`gcloud run jobs delete ${name} --region=${this.region} ${projectFlag} --quiet`);
deletedCount++;
}
}
}
return `Garbage collected ${deletedCount} Cloud Run jobs`;
}
catch (error) {
orchestrator_logger_1.default.logWarning(`[GCP Cloud Run] Garbage collection failed: ${error.message}`);
return '';
}
}
async listResources() {
const projectFlag = this.project ? `--project=${this.project}` : '';
try {
const jobsJson = await orchestrator_system_1.OrchestratorSystem.Run(`gcloud run jobs list --region=${this.region} ${projectFlag} --filter="metadata.name~unity-build-" --format="json(metadata.name)"`, false, true);
const jobs = JSON.parse(jobsJson || '[]');
return jobs.map((job) => ({ Name: job.metadata?.name || '' }));
}
catch {
return [];
}
}
listWorkflow() {
throw new Error('[GCP Cloud Run] listWorkflow not implemented for this experimental provider');
}
async watchWorkflow() {
throw new Error('[GCP Cloud Run] watchWorkflow not implemented for this experimental provider');
}
}
exports["default"] = GcpCloudRunProvider;
/***/ }), /***/ }),
/***/ 66990: /***/ 66990:
@@ -7061,6 +7911,8 @@ async function loadProvider(providerSource, buildParameters) {
'local-docker': './docker', 'local-docker': './docker',
'local-system': './local', 'local-system': './local',
local: './local', local: './local',
'gcp-cloud-run': './gcp-cloud-run',
'azure-aci': './azure-aci',
}; };
modulePath = providerModuleMap[providerSource] || providerSource; modulePath = providerModuleMap[providerSource] || providerSource;
orchestrator_logger_1.default.log(`Loading provider from module path: ${modulePath}`); orchestrator_logger_1.default.log(`Loading provider from module path: ${modulePath}`);
@@ -7125,7 +7977,7 @@ class ProviderLoader {
* @returns string[] - Array of available provider names * @returns string[] - Array of available provider names
*/ */
static getAvailableProviders() { static getAvailableProviders() {
return ['aws', 'k8s', 'test', 'local-docker', 'local-system', 'local']; return ['aws', 'k8s', 'test', 'local-docker', 'local-system', 'local', 'gcp-cloud-run', 'azure-aci'];
} }
/** /**
* Cleans up old cached repositories * Cleans up old cached repositories
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -83,9 +83,7 @@ class AzureAciProvider implements ProviderInterface {
'[Azure ACI] Storage type "azure-files" requires azureStorageAccount to be set.', '[Azure ACI] Storage type "azure-files" requires azureStorageAccount to be set.',
); );
} else { } else {
OrchestratorLogger.log( OrchestratorLogger.log(`[Azure ACI] File Share: ${this.storageAccount}/${this.fileShareName} (SMB)`);
`[Azure ACI] File Share: ${this.storageAccount}/${this.fileShareName} (SMB)`,
);
} }
break; break;
case 'azure-files-nfs': case 'azure-files-nfs':
@@ -95,26 +93,22 @@ class AzureAciProvider implements ProviderInterface {
); );
} }
if (!this.subnetId) { if (!this.subnetId) {
OrchestratorLogger.logWarning( OrchestratorLogger.logWarning('[Azure ACI] NFS file shares require VNet integration. Set azureSubnetId.');
'[Azure ACI] NFS file shares require VNet integration. Set azureSubnetId.',
);
} else { } else {
OrchestratorLogger.log( OrchestratorLogger.log(`[Azure ACI] File Share: ${this.storageAccount}/${this.fileShareName} (NFS 4.1)`);
`[Azure ACI] File Share: ${this.storageAccount}/${this.fileShareName} (NFS 4.1)`,
);
} }
break; break;
case 'blob-copy': case 'blob-copy':
if (!this.storageAccount) { if (!this.storageAccount) {
OrchestratorLogger.logWarning( OrchestratorLogger.logWarning('[Azure ACI] Storage type "blob-copy" requires azureStorageAccount to be set.');
'[Azure ACI] Storage type "blob-copy" requires azureStorageAccount to be set.',
);
} else { } else {
OrchestratorLogger.log(`[Azure ACI] Blob container: ${this.storageAccount}/${this.blobContainer}`); OrchestratorLogger.log(`[Azure ACI] Blob container: ${this.storageAccount}/${this.blobContainer}`);
} }
break; break;
case 'in-memory': case 'in-memory':
OrchestratorLogger.log(`[Azure ACI] In-memory volume (emptyDir): limited by ${this.memoryGb}GB container memory`); OrchestratorLogger.log(
`[Azure ACI] In-memory volume (emptyDir): limited by ${this.memoryGb}GB container memory`,
);
break; break;
default: default:
OrchestratorLogger.logWarning( OrchestratorLogger.logWarning(
@@ -155,17 +149,11 @@ class AzureAciProvider implements ProviderInterface {
// Ensure resource group exists // Ensure resource group exists
if (this.resourceGroup) { if (this.resourceGroup) {
try { try {
await OrchestratorSystem.Run( await OrchestratorSystem.Run(`az group show --name "${this.resourceGroup}" --output json`, false, true);
`az group show --name "${this.resourceGroup}" --output json`,
false,
true,
);
OrchestratorLogger.log(`[Azure ACI] Resource group ${this.resourceGroup} exists`); OrchestratorLogger.log(`[Azure ACI] Resource group ${this.resourceGroup} exists`);
} catch { } catch {
OrchestratorLogger.log(`[Azure ACI] Creating resource group ${this.resourceGroup}`); OrchestratorLogger.log(`[Azure ACI] Creating resource group ${this.resourceGroup}`);
await OrchestratorSystem.Run( await OrchestratorSystem.Run(`az group create --name "${this.resourceGroup}" --location "${this.location}"`);
`az group create --name "${this.resourceGroup}" --location "${this.location}"`,
);
} }
} }
@@ -325,15 +313,17 @@ class AzureAciProvider implements ProviderInterface {
OrchestratorLogger.log(`[Azure ACI] Running task for build ${buildGuid}`); OrchestratorLogger.log(`[Azure ACI] Running task for build ${buildGuid}`);
ResourceTracking.logAllocationSummary('azure-aci task'); ResourceTracking.logAllocationSummary('azure-aci task');
const containerName = `unity-build-${buildGuid}`.toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 63); const containerName = `unity-build-${buildGuid}`
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.slice(0, 63);
// Build environment variable flags // Build environment variable flags
const allEnvVars = [ const allEnvVars = [
...environment.map((env) => `${env.name}=${env.value}`), ...environment.map((env) => `${env.name}=${env.value}`),
...secrets.map((s) => `${s.EnvironmentVariable}=${s.ParameterValue}`), ...secrets.map((s) => `${s.EnvironmentVariable}=${s.ParameterValue}`),
]; ];
const envFlag = const envFlag = allEnvVars.length > 0 ? `--environment-variables ${allEnvVars.map((e) => `"${e}"`).join(' ')}` : '';
allEnvVars.length > 0 ? `--environment-variables ${allEnvVars.map((e) => `"${e}"`).join(' ')}` : '';
// Build volume flags based on storage type // Build volume flags based on storage type
const volumeFlags = await this.buildVolumeFlags(mountdir); const volumeFlags = await this.buildVolumeFlags(mountdir);
@@ -402,9 +392,7 @@ class AzureAciProvider implements ProviderInterface {
const state = JSON.parse(stateJson); const state = JSON.parse(stateJson);
const containerState = const containerState =
state.containers?.[0]?.instanceView?.currentState?.state || state.containers?.[0]?.instanceView?.currentState?.state || state.instanceView?.state || 'Unknown';
state.instanceView?.state ||
'Unknown';
const provisioningState = state.provisioningState || 'Unknown'; const provisioningState = state.provisioningState || 'Unknown';
// Stream logs incrementally // Stream logs incrementally
@@ -452,10 +440,7 @@ class AzureAciProvider implements ProviderInterface {
throw new Error(`[Azure ACI] Container provisioning failed: ${detail}`); throw new Error(`[Azure ACI] Container provisioning failed: ${detail}`);
} }
} catch (error: any) { } catch (error: any) {
if ( if (error.message?.includes('Container provisioning failed') || error.message?.includes('exited with code')) {
error.message?.includes('Container provisioning failed') ||
error.message?.includes('exited with code')
) {
throw error; throw error;
} }
OrchestratorLogger.logWarning(`[Azure ACI] Polling error: ${error.message}`); OrchestratorLogger.logWarning(`[Azure ACI] Polling error: ${error.message}`);
@@ -500,9 +485,7 @@ class AzureAciProvider implements ProviderInterface {
const name = container.name || ''; const name = container.name || '';
if (!name.startsWith('unity-build-')) continue; if (!name.startsWith('unity-build-')) continue;
const createdAt = new Date( const createdAt = new Date(container.tags?.createdAt || container.properties?.provisioningState || 0);
container.tags?.createdAt || container.properties?.provisioningState || 0,
);
const state = container.containers?.[0]?.instanceView?.currentState?.state || ''; const state = container.containers?.[0]?.instanceView?.currentState?.state || '';
if (state === 'Terminated' || createdAt < cutoffDate) { if (state === 'Terminated' || createdAt < cutoffDate) {
@@ -228,7 +228,10 @@ class GcpCloudRunProvider implements ProviderInterface {
OrchestratorLogger.log(`[GCP Cloud Run] Running task for build ${buildGuid}`); OrchestratorLogger.log(`[GCP Cloud Run] Running task for build ${buildGuid}`);
ResourceTracking.logAllocationSummary('gcp-cloud-run task'); ResourceTracking.logAllocationSummary('gcp-cloud-run task');
const jobName = `unity-build-${buildGuid}`.toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 63); const jobName = `unity-build-${buildGuid}`
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.slice(0, 63);
const projectFlag = this.project ? `--project=${this.project}` : ''; const projectFlag = this.project ? `--project=${this.project}` : '';
// Build environment variable flags // Build environment variable flags