mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-06-13 09:23:52 -07:00
Merge remote-tracking branch 'origin/feature/ci-platform-providers' into release/lts-2.0.0
# Conflicts: # action.yml # dist/index.js.map # src/model/build-parameters.ts # src/model/input.ts # src/model/orchestrator/orchestrator.ts
This commit is contained in:
+893
@@ -376,6 +376,25 @@ class BuildParameters {
|
||||
cacheUnityInstallationOnMac: input_1.default.cacheUnityInstallationOnMac,
|
||||
unityHubVersionOnMac: input_1.default.unityHubVersionOnMac,
|
||||
dockerWorkspacePath: input_1.default.dockerWorkspacePath,
|
||||
// Remote PowerShell provider
|
||||
remotePowershellHost: input_1.default.remotePowershellHost,
|
||||
remotePowershellCredential: input_1.default.remotePowershellCredential,
|
||||
remotePowershellTransport: input_1.default.remotePowershellTransport,
|
||||
// GitHub Actions provider
|
||||
githubActionsRepo: input_1.default.githubActionsRepo,
|
||||
githubActionsWorkflow: input_1.default.githubActionsWorkflow,
|
||||
githubActionsToken: input_1.default.githubActionsToken,
|
||||
githubActionsRef: input_1.default.githubActionsRef,
|
||||
// GitLab CI provider
|
||||
gitlabProjectId: input_1.default.gitlabProjectId,
|
||||
gitlabTriggerToken: input_1.default.gitlabTriggerToken,
|
||||
gitlabApiUrl: input_1.default.gitlabApiUrl,
|
||||
gitlabRef: input_1.default.gitlabRef,
|
||||
// Ansible provider
|
||||
ansibleInventory: input_1.default.ansibleInventory,
|
||||
ansiblePlaybook: input_1.default.ansiblePlaybook,
|
||||
ansibleExtraVars: input_1.default.ansibleExtraVars,
|
||||
ansibleVaultPassword: input_1.default.ansibleVaultPassword,
|
||||
};
|
||||
}
|
||||
static parseBuildFile(filename, platform, androidExportType) {
|
||||
@@ -1827,6 +1846,63 @@ class Input {
|
||||
static get skipActivation() {
|
||||
return Input.getInput('skipActivation')?.toLowerCase() ?? 'false';
|
||||
}
|
||||
// ### ### ###
|
||||
// Remote PowerShell provider
|
||||
// ### ### ###
|
||||
static get remotePowershellHost() {
|
||||
return Input.getInput('remotePowershellHost') ?? '';
|
||||
}
|
||||
static get remotePowershellCredential() {
|
||||
return Input.getInput('remotePowershellCredential') ?? '';
|
||||
}
|
||||
static get remotePowershellTransport() {
|
||||
return Input.getInput('remotePowershellTransport') ?? 'wsman';
|
||||
}
|
||||
// ### ### ###
|
||||
// GitHub Actions provider
|
||||
// ### ### ###
|
||||
static get githubActionsRepo() {
|
||||
return Input.getInput('githubActionsRepo') ?? '';
|
||||
}
|
||||
static get githubActionsWorkflow() {
|
||||
return Input.getInput('githubActionsWorkflow') ?? '';
|
||||
}
|
||||
static get githubActionsToken() {
|
||||
return Input.getInput('githubActionsToken') ?? '';
|
||||
}
|
||||
static get githubActionsRef() {
|
||||
return Input.getInput('githubActionsRef') ?? 'main';
|
||||
}
|
||||
// ### ### ###
|
||||
// GitLab CI provider
|
||||
// ### ### ###
|
||||
static get gitlabProjectId() {
|
||||
return Input.getInput('gitlabProjectId') ?? '';
|
||||
}
|
||||
static get gitlabTriggerToken() {
|
||||
return Input.getInput('gitlabTriggerToken') ?? '';
|
||||
}
|
||||
static get gitlabApiUrl() {
|
||||
return Input.getInput('gitlabApiUrl') ?? 'https://gitlab.com';
|
||||
}
|
||||
static get gitlabRef() {
|
||||
return Input.getInput('gitlabRef') ?? 'main';
|
||||
}
|
||||
// ### ### ###
|
||||
// Ansible provider
|
||||
// ### ### ###
|
||||
static get ansibleInventory() {
|
||||
return Input.getInput('ansibleInventory') ?? '';
|
||||
}
|
||||
static get ansiblePlaybook() {
|
||||
return Input.getInput('ansiblePlaybook') ?? '';
|
||||
}
|
||||
static get ansibleExtraVars() {
|
||||
return Input.getInput('ansibleExtraVars') ?? '';
|
||||
}
|
||||
static get ansibleVaultPassword() {
|
||||
return Input.getInput('ansibleVaultPassword') ?? '';
|
||||
}
|
||||
static ToEnvVarFormat(input) {
|
||||
if (input.toUpperCase() === input) {
|
||||
return input;
|
||||
@@ -2639,6 +2715,10 @@ const core = __importStar(__nccwpck_require__(42186));
|
||||
const test_1 = __importDefault(__nccwpck_require__(6389));
|
||||
const local_1 = __importDefault(__nccwpck_require__(48195));
|
||||
const docker_1 = __importDefault(__nccwpck_require__(91739));
|
||||
const remote_powershell_1 = __importDefault(__nccwpck_require__(90732));
|
||||
const github_actions_1 = __importDefault(__nccwpck_require__(57511));
|
||||
const gitlab_ci_1 = __importDefault(__nccwpck_require__(28103));
|
||||
const ansible_1 = __importDefault(__nccwpck_require__(72073));
|
||||
const provider_loader_1 = __importDefault(__nccwpck_require__(50822));
|
||||
const github_1 = __importDefault(__nccwpck_require__(83654));
|
||||
const shared_workspace_locking_1 = __importDefault(__nccwpck_require__(54222));
|
||||
@@ -2757,6 +2837,18 @@ class Orchestrator {
|
||||
case 'local':
|
||||
Orchestrator.Provider = new local_1.default();
|
||||
break;
|
||||
case 'remote-powershell':
|
||||
Orchestrator.Provider = new remote_powershell_1.default(Orchestrator.buildParameters);
|
||||
break;
|
||||
case 'github-actions':
|
||||
Orchestrator.Provider = new github_actions_1.default(Orchestrator.buildParameters);
|
||||
break;
|
||||
case 'gitlab-ci':
|
||||
Orchestrator.Provider = new gitlab_ci_1.default(Orchestrator.buildParameters);
|
||||
break;
|
||||
case 'ansible':
|
||||
Orchestrator.Provider = new ansible_1.default(Orchestrator.buildParameters);
|
||||
break;
|
||||
default:
|
||||
// Try to load provider using the dynamic loader for unknown providers
|
||||
try {
|
||||
@@ -2906,6 +2998,196 @@ Orchestrator.validateAwsTemplates = false;
|
||||
exports["default"] = Orchestrator;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 72073:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const core = __importStar(__nccwpck_require__(42186));
|
||||
const orchestrator_system_1 = __nccwpck_require__(9744);
|
||||
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
|
||||
const provider_resource_1 = __nccwpck_require__(72538);
|
||||
/**
|
||||
* Ansible provider — executes Unity builds via Ansible playbooks
|
||||
* against managed inventory.
|
||||
*
|
||||
* Use case: Teams with existing Ansible infrastructure for server
|
||||
* management who want to leverage their inventory for build distribution.
|
||||
*/
|
||||
class AnsibleProvider {
|
||||
constructor(buildParameters) {
|
||||
this.buildParameters = buildParameters;
|
||||
this.inventory = buildParameters.ansibleInventory || '';
|
||||
this.playbook = buildParameters.ansiblePlaybook || '';
|
||||
this.extraVariables = buildParameters.ansibleExtraVars || '';
|
||||
this.vaultPassword = buildParameters.ansibleVaultPassword || '';
|
||||
}
|
||||
async setupWorkflow(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildGuid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[Ansible] Setting up playbook execution`);
|
||||
if (!this.inventory) {
|
||||
throw new Error('ansibleInventory is required for the ansible provider');
|
||||
}
|
||||
// Verify ansible is available
|
||||
try {
|
||||
const version = await orchestrator_system_1.OrchestratorSystem.Run('ansible --version | head -1');
|
||||
orchestrator_logger_1.default.log(`[Ansible] ${version.trim()}`);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Ansible not found on PATH: ${error.message || error}`);
|
||||
}
|
||||
// Verify ansible-playbook binary exists (may be separate from ansible)
|
||||
try {
|
||||
await orchestrator_system_1.OrchestratorSystem.Run('command -v ansible-playbook || which ansible-playbook || where ansible-playbook');
|
||||
orchestrator_logger_1.default.log(`[Ansible] ansible-playbook binary verified`);
|
||||
}
|
||||
catch (error) {
|
||||
core.error('ansible-playbook not found. Install Ansible or ensure it is in PATH.');
|
||||
throw new Error(`ansible-playbook not found on PATH: ${error.message || error}`);
|
||||
}
|
||||
// Verify inventory exists
|
||||
try {
|
||||
await orchestrator_system_1.OrchestratorSystem.Run(`test -e "${this.inventory}"`);
|
||||
}
|
||||
catch {
|
||||
throw new Error(`Inventory not found: ${this.inventory}`);
|
||||
}
|
||||
}
|
||||
async runTaskInWorkflow(buildGuid, image, commands, mountdir, workingdir, environment, secrets) {
|
||||
orchestrator_logger_1.default.log(`[Ansible] Running playbook against inventory ${this.inventory}`);
|
||||
if (!this.playbook) {
|
||||
throw new Error('ansiblePlaybook is required — no default playbook is provided yet. ' +
|
||||
'Provide a playbook that accepts build_guid, build_image, build_commands, mount_dir, and working_dir variables.');
|
||||
}
|
||||
// Build extra-vars JSON
|
||||
// These use snake_case because they are Ansible variable names passed to playbooks
|
||||
const playbookVariables = {
|
||||
// eslint-disable-next-line camelcase
|
||||
build_guid: buildGuid,
|
||||
// eslint-disable-next-line camelcase
|
||||
build_image: image,
|
||||
// eslint-disable-next-line camelcase
|
||||
build_commands: commands,
|
||||
// eslint-disable-next-line camelcase
|
||||
mount_dir: mountdir,
|
||||
// eslint-disable-next-line camelcase
|
||||
working_dir: workingdir,
|
||||
};
|
||||
for (const element of environment) {
|
||||
playbookVariables[element.name.toLowerCase()] = element.value;
|
||||
}
|
||||
// Merge user-provided extra vars
|
||||
if (this.extraVariables) {
|
||||
try {
|
||||
const userVariables = JSON.parse(this.extraVariables);
|
||||
Object.assign(playbookVariables, userVariables);
|
||||
}
|
||||
catch {
|
||||
orchestrator_logger_1.default.logWarning(`[Ansible] Failed to parse ansibleExtraVars as JSON, using as-is`);
|
||||
}
|
||||
}
|
||||
const extraVariablesJson = JSON.stringify(playbookVariables).replace(/'/g, "'\\''");
|
||||
// Build ansible-playbook command
|
||||
const commandParts = [
|
||||
'ansible-playbook',
|
||||
`-i "${this.inventory}"`,
|
||||
`"${this.playbook}"`,
|
||||
`-e '${extraVariablesJson}'`,
|
||||
'--no-color',
|
||||
];
|
||||
if (this.vaultPassword) {
|
||||
commandParts.push(`--vault-password-file "${this.vaultPassword}"`);
|
||||
}
|
||||
// Add secret variables as extra environment
|
||||
const environmentPrefix = secrets
|
||||
.map((secret) => `${secret.EnvironmentVariable}='${secret.ParameterValue}'`)
|
||||
.join(' ');
|
||||
const fullCommand = environmentPrefix ? `${environmentPrefix} ${commandParts.join(' ')}` : commandParts.join(' ');
|
||||
try {
|
||||
const output = await orchestrator_system_1.OrchestratorSystem.Run(fullCommand);
|
||||
orchestrator_logger_1.default.log(`[Ansible] Playbook completed successfully`);
|
||||
return output;
|
||||
}
|
||||
catch (error) {
|
||||
orchestrator_logger_1.default.logWarning(`[Ansible] Playbook failed: ${error.message || error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async cleanupWorkflow(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[Ansible] Cleanup complete`);
|
||||
}
|
||||
async garbageCollect(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
filter,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
previewOnly,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
olderThan,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
fullCache,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
baseDependencies) {
|
||||
return '';
|
||||
}
|
||||
async listResources() {
|
||||
if (!this.inventory)
|
||||
return [];
|
||||
const resource = new provider_resource_1.ProviderResource();
|
||||
resource.Name = this.inventory;
|
||||
return [resource];
|
||||
}
|
||||
async listWorkflow() {
|
||||
return [];
|
||||
}
|
||||
async watchWorkflow() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
exports["default"] = AnsibleProvider;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 41878:
|
||||
@@ -5064,6 +5346,455 @@ find ${sharedFolder} -maxdepth 1 -type f -name "test-*" -exec cp -a {} /github/w
|
||||
exports["default"] = LocalDockerOrchestrator;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 57511:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const core = __importStar(__nccwpck_require__(42186));
|
||||
const orchestrator_system_1 = __nccwpck_require__(9744);
|
||||
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
|
||||
const provider_resource_1 = __nccwpck_require__(72538);
|
||||
const provider_workflow_1 = __nccwpck_require__(60511);
|
||||
const MAX_POLLING_DURATION_MS = 14400000; // 4 hours
|
||||
/**
|
||||
* GitHub Actions provider — triggers builds as workflow_dispatch events
|
||||
* on a target repository via the GitHub API.
|
||||
*
|
||||
* Use case: Distribute builds across orgs, use specialized runner pools,
|
||||
* or trigger builds in repos with Unity licenses.
|
||||
*/
|
||||
class GitHubActionsProvider {
|
||||
constructor(buildParameters) {
|
||||
this.runId = 0;
|
||||
this.buildParameters = buildParameters;
|
||||
this.repo = buildParameters.githubActionsRepo || '';
|
||||
this.workflow = buildParameters.githubActionsWorkflow || '';
|
||||
this.token = buildParameters.githubActionsToken || '';
|
||||
this.ref = buildParameters.githubActionsRef || 'main';
|
||||
}
|
||||
async setupWorkflow(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildGuid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Setting up workflow dispatch to ${this.repo}`);
|
||||
if (!this.repo || !this.workflow) {
|
||||
throw new Error('githubActionsRepo and githubActionsWorkflow are required for the github-actions provider');
|
||||
}
|
||||
if (!this.token) {
|
||||
throw new Error('githubActionsToken is required (PAT with actions:write scope)');
|
||||
}
|
||||
// Verify repository and workflow exist
|
||||
try {
|
||||
const result = await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh api repos/${this.repo}/actions/workflows/${this.workflow} --jq '.id'`);
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Workflow verified: ${this.workflow} (ID: ${result.trim()})`);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to verify workflow ${this.workflow} in ${this.repo}: ${error.message || error}`);
|
||||
}
|
||||
}
|
||||
async runTaskInWorkflow(buildGuid, image, commands, mountdir, workingdir, environment,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
secrets) {
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Dispatching workflow ${this.workflow} on ${this.repo}@${this.ref}`);
|
||||
// Build inputs payload
|
||||
const inputs = {
|
||||
buildGuid,
|
||||
image,
|
||||
commands: Buffer.from(commands).toString('base64'),
|
||||
mountdir,
|
||||
workingdir,
|
||||
};
|
||||
// Add environment variables as a JSON input
|
||||
if (environment.length > 0) {
|
||||
inputs.environment = JSON.stringify(environment.map((element) => ({ name: element.name, value: element.value })));
|
||||
}
|
||||
// Record the time before dispatch to identify the run
|
||||
const beforeDispatch = new Date().toISOString();
|
||||
// Dispatch the workflow
|
||||
const inputsJson = JSON.stringify(inputs).replace(/'/g, "'\\''");
|
||||
try {
|
||||
await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh api repos/${this.repo}/actions/workflows/${this.workflow}/dispatches -X POST -f ref='${this.ref}' -f "inputs=${inputsJson}"`);
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Workflow dispatched`);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to dispatch workflow: ${error.message || error}`);
|
||||
}
|
||||
// Poll for the run to appear
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Waiting for workflow run to start...`);
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
while (attempts < maxAttempts) {
|
||||
attempts++;
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
try {
|
||||
const runsJson = await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh api "repos/${this.repo}/actions/workflows/${this.workflow}/runs?created=>${beforeDispatch}&per_page=5" --jq '.workflow_runs[0] | {id, status, conclusion}'`, true);
|
||||
const run = JSON.parse(runsJson.trim());
|
||||
if (run.id) {
|
||||
this.runId = run.id;
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Run started: ${this.runId} (status: ${run.status})`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Run not yet available
|
||||
}
|
||||
}
|
||||
if (!this.runId) {
|
||||
throw new Error(`Workflow run did not start within ${maxAttempts * 10}s`);
|
||||
}
|
||||
// Poll until completion and stream logs (with maximum duration guard)
|
||||
let status = 'in_progress';
|
||||
const pollingStartTime = Date.now();
|
||||
const runUrl = `https://github.com/${this.repo}/actions/runs/${this.runId}`;
|
||||
while (status === 'in_progress' || status === 'queued') {
|
||||
const elapsedMs = Date.now() - pollingStartTime;
|
||||
if (elapsedMs >= MAX_POLLING_DURATION_MS) {
|
||||
const hours = Math.round(MAX_POLLING_DURATION_MS / 3600000);
|
||||
const message = `GitHub Actions workflow did not complete within ${hours} hours. Run URL: ${runUrl}`;
|
||||
core.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 15000));
|
||||
try {
|
||||
const statusJson = await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh api repos/${this.repo}/actions/runs/${this.runId} --jq '{status, conclusion}'`, true);
|
||||
const result = JSON.parse(statusJson.trim());
|
||||
status = result.status;
|
||||
if (status === 'completed') {
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Run ${this.runId} completed: ${result.conclusion}`);
|
||||
if (result.conclusion !== 'success') {
|
||||
throw new Error(`Workflow run failed with conclusion: ${result.conclusion}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Run ${this.runId} status: ${status}`);
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message && error.message.includes('conclusion')) {
|
||||
throw error;
|
||||
}
|
||||
if (error.message && error.message.includes('did not complete within')) {
|
||||
throw error;
|
||||
}
|
||||
orchestrator_logger_1.default.logWarning(`[GitHubActions] Status check error: ${error.message || error}`);
|
||||
}
|
||||
}
|
||||
// Fetch logs
|
||||
try {
|
||||
const logs = await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh run view ${this.runId} --repo ${this.repo} --log`, true);
|
||||
return logs;
|
||||
}
|
||||
catch {
|
||||
return `Run ${this.runId} completed successfully (logs unavailable)`;
|
||||
}
|
||||
}
|
||||
async cleanupWorkflow(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[GitHubActions] Cleanup complete (no resources to tear down)`);
|
||||
}
|
||||
async garbageCollect(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
filter,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
previewOnly,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
olderThan,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
fullCache,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
baseDependencies) {
|
||||
return '';
|
||||
}
|
||||
async listResources() {
|
||||
if (!this.repo || !this.token)
|
||||
return [];
|
||||
try {
|
||||
const runnersJson = await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh api repos/${this.repo}/actions/runners --jq '.runners[] | .name'`, true);
|
||||
return runnersJson
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((name) => {
|
||||
const resource = new provider_resource_1.ProviderResource();
|
||||
resource.Name = name.trim();
|
||||
return resource;
|
||||
});
|
||||
}
|
||||
catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
async listWorkflow() {
|
||||
if (!this.repo || !this.token)
|
||||
return [];
|
||||
try {
|
||||
const runsJson = await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh api repos/${this.repo}/actions/runs?per_page=10 --jq '.workflow_runs[] | .name'`, true);
|
||||
return runsJson
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((name) => {
|
||||
const workflow = new provider_workflow_1.ProviderWorkflow();
|
||||
workflow.Name = name.trim();
|
||||
return workflow;
|
||||
});
|
||||
}
|
||||
catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
async watchWorkflow() {
|
||||
if (!this.runId)
|
||||
return 'No active run to watch';
|
||||
try {
|
||||
return await orchestrator_system_1.OrchestratorSystem.Run(`GH_TOKEN=${this.token} gh run watch ${this.runId} --repo ${this.repo}`, true);
|
||||
}
|
||||
catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
exports["default"] = GitHubActionsProvider;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 28103:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const core = __importStar(__nccwpck_require__(42186));
|
||||
const orchestrator_system_1 = __nccwpck_require__(9744);
|
||||
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
|
||||
const provider_workflow_1 = __nccwpck_require__(60511);
|
||||
const MAX_POLLING_DURATION_MS = 14400000; // 4 hours
|
||||
/**
|
||||
* GitLab CI provider — triggers builds as GitLab CI pipelines
|
||||
* via the GitLab API.
|
||||
*
|
||||
* Use case: Teams using GitLab CI, hybrid GitHub/GitLab setups,
|
||||
* or GitLab runners with Unity licenses.
|
||||
*/
|
||||
class GitLabCIProvider {
|
||||
constructor(buildParameters) {
|
||||
this.pipelineId = 0;
|
||||
this.buildParameters = buildParameters;
|
||||
this.projectId = buildParameters.gitlabProjectId || '';
|
||||
this.triggerToken = buildParameters.gitlabTriggerToken || '';
|
||||
this.apiUrl = (buildParameters.gitlabApiUrl || 'https://gitlab.com').replace(/\/+$/, '');
|
||||
this.ref = buildParameters.gitlabRef || 'main';
|
||||
}
|
||||
async setupWorkflow(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildGuid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[GitLabCI] Setting up pipeline trigger for project ${this.projectId}`);
|
||||
if (!this.projectId || !this.triggerToken) {
|
||||
throw new Error('gitlabProjectId and gitlabTriggerToken are required for the gitlab-ci provider');
|
||||
}
|
||||
// Verify project access
|
||||
const encodedProject = encodeURIComponent(this.projectId);
|
||||
try {
|
||||
await orchestrator_system_1.OrchestratorSystem.Run(`curl -sf -H "PRIVATE-TOKEN: ${this.triggerToken}" "${this.apiUrl}/api/v4/projects/${encodedProject}" -o /dev/null`);
|
||||
orchestrator_logger_1.default.log(`[GitLabCI] Project access verified`);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to access GitLab project ${this.projectId}: ${error.message || error}`);
|
||||
}
|
||||
}
|
||||
async runTaskInWorkflow(buildGuid, image, commands, mountdir, workingdir, environment,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
secrets) {
|
||||
orchestrator_logger_1.default.log(`[GitLabCI] Triggering pipeline on project ${this.projectId}@${this.ref}`);
|
||||
const encodedProject = encodeURIComponent(this.projectId);
|
||||
// Build variables for the pipeline
|
||||
const pipelineVariables = [
|
||||
`-f "variables[BUILD_GUID]=${buildGuid}"`,
|
||||
`-f "variables[BUILD_IMAGE]=${image}"`,
|
||||
`-f "variables[BUILD_COMMANDS]=${Buffer.from(commands).toString('base64')}"`,
|
||||
`-f "variables[MOUNT_DIR]=${mountdir}"`,
|
||||
`-f "variables[WORKING_DIR]=${workingdir}"`,
|
||||
];
|
||||
for (const element of environment) {
|
||||
pipelineVariables.push(`-f "variables[${element.name}]=${element.value}"`);
|
||||
}
|
||||
// Trigger pipeline
|
||||
try {
|
||||
const response = await orchestrator_system_1.OrchestratorSystem.Run(`curl -sf -X POST "${this.apiUrl}/api/v4/projects/${encodedProject}/trigger/pipeline" -f "token=${this.triggerToken}" -f "ref=${this.ref}" ${pipelineVariables.join(' ')}`);
|
||||
const pipeline = JSON.parse(response);
|
||||
this.pipelineId = pipeline.id;
|
||||
orchestrator_logger_1.default.log(`[GitLabCI] Pipeline triggered: ${this.pipelineId} (status: ${pipeline.status})`);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to trigger pipeline: ${error.message || error}`);
|
||||
}
|
||||
// Poll until completion (with maximum duration guard)
|
||||
let status = 'pending';
|
||||
const terminalStatuses = new Set(['success', 'failed', 'canceled', 'skipped']);
|
||||
const pollingStartTime = Date.now();
|
||||
const pipelineUrl = `${this.apiUrl}/${this.projectId}/-/pipelines/${this.pipelineId}`;
|
||||
while (!terminalStatuses.has(status)) {
|
||||
const elapsedMs = Date.now() - pollingStartTime;
|
||||
if (elapsedMs >= MAX_POLLING_DURATION_MS) {
|
||||
const hours = Math.round(MAX_POLLING_DURATION_MS / 3600000);
|
||||
const message = `GitLab CI pipeline did not complete within ${hours} hours. Pipeline URL: ${pipelineUrl}`;
|
||||
core.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 15000));
|
||||
try {
|
||||
const statusResponse = await orchestrator_system_1.OrchestratorSystem.Run(`curl -sf -H "PRIVATE-TOKEN: ${this.triggerToken}" "${this.apiUrl}/api/v4/projects/${encodedProject}/pipelines/${this.pipelineId}"`, true);
|
||||
const pipelineStatus = JSON.parse(statusResponse);
|
||||
status = pipelineStatus.status;
|
||||
orchestrator_logger_1.default.log(`[GitLabCI] Pipeline ${this.pipelineId} status: ${status}`);
|
||||
}
|
||||
catch (error) {
|
||||
orchestrator_logger_1.default.logWarning(`[GitLabCI] Status check error: ${error.message || error}`);
|
||||
}
|
||||
}
|
||||
if (status !== 'success') {
|
||||
throw new Error(`Pipeline ${this.pipelineId} finished with status: ${status}`);
|
||||
}
|
||||
// Fetch job logs
|
||||
try {
|
||||
const jobsResponse = await orchestrator_system_1.OrchestratorSystem.Run(`curl -sf -H "PRIVATE-TOKEN: ${this.triggerToken}" "${this.apiUrl}/api/v4/projects/${encodedProject}/pipelines/${this.pipelineId}/jobs"`, true);
|
||||
const jobs = JSON.parse(jobsResponse);
|
||||
const logs = [];
|
||||
for (const job of jobs) {
|
||||
try {
|
||||
const jobLog = await orchestrator_system_1.OrchestratorSystem.Run(`curl -sf -H "PRIVATE-TOKEN: ${this.triggerToken}" "${this.apiUrl}/api/v4/projects/${encodedProject}/jobs/${job.id}/trace"`, true);
|
||||
logs.push(`=== Job: ${job.name} (${job.status}) ===\n${jobLog}`);
|
||||
}
|
||||
catch {
|
||||
logs.push(`=== Job: ${job.name} (${job.status}) === (logs unavailable)`);
|
||||
}
|
||||
}
|
||||
return logs.join('\n\n');
|
||||
}
|
||||
catch {
|
||||
return `Pipeline ${this.pipelineId} completed successfully (logs unavailable)`;
|
||||
}
|
||||
}
|
||||
async cleanupWorkflow(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[GitLabCI] Cleanup complete`);
|
||||
}
|
||||
async garbageCollect(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
filter,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
previewOnly,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
olderThan,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
fullCache,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
baseDependencies) {
|
||||
return '';
|
||||
}
|
||||
async listResources() {
|
||||
return [];
|
||||
}
|
||||
async listWorkflow() {
|
||||
if (!this.projectId || !this.triggerToken)
|
||||
return [];
|
||||
try {
|
||||
const encodedProject = encodeURIComponent(this.projectId);
|
||||
const response = await orchestrator_system_1.OrchestratorSystem.Run(`curl -sf -H "PRIVATE-TOKEN: ${this.triggerToken}" "${this.apiUrl}/api/v4/projects/${encodedProject}/pipelines?per_page=10"`, true);
|
||||
return JSON.parse(response).map((pipeline) => {
|
||||
const workflow = new provider_workflow_1.ProviderWorkflow();
|
||||
workflow.Name = `Pipeline #${pipeline.id} (${pipeline.status})`;
|
||||
return workflow;
|
||||
});
|
||||
}
|
||||
catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
async watchWorkflow() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
exports["default"] = GitLabCIProvider;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 66990:
|
||||
@@ -7284,6 +8015,20 @@ class ProviderLoader {
|
||||
exports.ProviderLoader = ProviderLoader;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 72538:
|
||||
/***/ ((__unused_webpack_module, exports) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.ProviderResource = void 0;
|
||||
class ProviderResource {
|
||||
}
|
||||
exports.ProviderResource = ProviderResource;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 26162:
|
||||
@@ -7405,6 +8150,154 @@ function logProviderSource(source, parsed) {
|
||||
exports.logProviderSource = logProviderSource;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 60511:
|
||||
/***/ ((__unused_webpack_module, exports) => {
|
||||
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.ProviderWorkflow = void 0;
|
||||
class ProviderWorkflow {
|
||||
}
|
||||
exports.ProviderWorkflow = ProviderWorkflow;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 90732:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const orchestrator_system_1 = __nccwpck_require__(9744);
|
||||
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
|
||||
const provider_resource_1 = __nccwpck_require__(72538);
|
||||
/**
|
||||
* Remote PowerShell provider — executes Unity builds on remote machines
|
||||
* via PowerShell Remoting (WinRM or SSH).
|
||||
*
|
||||
* Use case: Teams with dedicated build machines not part of a CI system.
|
||||
*/
|
||||
class RemotePowershellProvider {
|
||||
constructor(buildParameters) {
|
||||
this.sessionId = '';
|
||||
this.buildParameters = buildParameters;
|
||||
this.host = buildParameters.remotePowershellHost || '';
|
||||
this.transport = buildParameters.remotePowershellTransport || 'wsman';
|
||||
this.credential = buildParameters.remotePowershellCredential || '';
|
||||
}
|
||||
async setupWorkflow(buildGuid,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[RemotePowershell] Setting up remote session to ${this.host} via ${this.transport}`);
|
||||
if (!this.host) {
|
||||
throw new Error('remotePowershellHost is required for the remote-powershell provider');
|
||||
}
|
||||
// Test connectivity
|
||||
const testCommand = this.buildPwshCommand(`Test-WSMan -ComputerName "${this.host}" -ErrorAction Stop`);
|
||||
try {
|
||||
await orchestrator_system_1.OrchestratorSystem.Run(testCommand);
|
||||
orchestrator_logger_1.default.log(`[RemotePowershell] Connection test passed`);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Failed to connect to remote host ${this.host}: ${error.message || error}`);
|
||||
}
|
||||
this.sessionId = buildGuid;
|
||||
orchestrator_logger_1.default.log(`[RemotePowershell] Session ${this.sessionId} ready`);
|
||||
}
|
||||
async runTaskInWorkflow(buildGuid, image, commands, mountdir, workingdir, environment, secrets) {
|
||||
orchestrator_logger_1.default.log(`[RemotePowershell] Executing task on ${this.host}`);
|
||||
// Build environment variable block for remote session
|
||||
const environmentBlock = environment.map((element) => `$env:${element.name} = '${element.value}'`).join('; ');
|
||||
const secretBlock = secrets
|
||||
.map((secret) => `$env:${secret.EnvironmentVariable} = '${secret.ParameterValue}'`)
|
||||
.join('; ');
|
||||
// Wrap commands for remote execution
|
||||
const remoteScript = [environmentBlock, secretBlock, `Set-Location "${workingdir}"`, commands]
|
||||
.filter(Boolean)
|
||||
.join('; ');
|
||||
const invokeCommand = this.buildInvokeCommand(remoteScript);
|
||||
try {
|
||||
const output = await orchestrator_system_1.OrchestratorSystem.Run(invokeCommand);
|
||||
orchestrator_logger_1.default.log(`[RemotePowershell] Task completed successfully`);
|
||||
return output;
|
||||
}
|
||||
catch (error) {
|
||||
orchestrator_logger_1.default.logWarning(`[RemotePowershell] Task failed: ${error.message || error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async cleanupWorkflow(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
buildParameters,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
branchName,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSecretsArray) {
|
||||
orchestrator_logger_1.default.log(`[RemotePowershell] Cleaning up session ${this.sessionId}`);
|
||||
// Remote sessions are stateless per invocation — no cleanup needed
|
||||
}
|
||||
async garbageCollect(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
filter,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
previewOnly,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
olderThan,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
fullCache,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
baseDependencies) {
|
||||
orchestrator_logger_1.default.log(`[RemotePowershell] Garbage collection not supported for remote PowerShell provider`);
|
||||
return '';
|
||||
}
|
||||
async listResources() {
|
||||
const resource = new provider_resource_1.ProviderResource();
|
||||
resource.Name = this.host;
|
||||
return [resource];
|
||||
}
|
||||
async listWorkflow() {
|
||||
return [];
|
||||
}
|
||||
async watchWorkflow() {
|
||||
return '';
|
||||
}
|
||||
buildPwshCommand(script) {
|
||||
return `pwsh -NoProfile -NonInteractive -Command "${script.replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
buildInvokeCommand(remoteScript) {
|
||||
const escapedScript = remoteScript.replace(/"/g, '\\"').replace(/'/g, "''");
|
||||
if (this.transport === 'ssh') {
|
||||
return `pwsh -NoProfile -NonInteractive -Command "Invoke-Command -HostName '${this.host}' -ScriptBlock { ${escapedScript} }"`;
|
||||
}
|
||||
// WinRM (default)
|
||||
// Split on the FIRST colon only — passwords may contain colons
|
||||
let credentialPart = '';
|
||||
if (this.credential) {
|
||||
const colonIndex = this.credential.indexOf(':');
|
||||
if (colonIndex === -1) {
|
||||
throw new Error('remotePowershellCredential must be in "username:password" format (no colon found)');
|
||||
}
|
||||
const user = this.credential.substring(0, colonIndex);
|
||||
const pass = this.credential.substring(colonIndex + 1);
|
||||
credentialPart = `-Credential (New-Object PSCredential('${user}', (ConvertTo-SecureString '${pass}' -AsPlainText -Force)))`;
|
||||
}
|
||||
return `pwsh -NoProfile -NonInteractive -Command "Invoke-Command -ComputerName '${this.host}' ${credentialPart} -ScriptBlock { ${escapedScript} }"`;
|
||||
}
|
||||
}
|
||||
exports["default"] = RemotePowershellProvider;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 6389:
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user