Sprint 13-14: §6.2 composite action + §6.4 build matrix + §6.5 secret injection
§6.2 - New composite action gitea/actions/local-ci-build/action.yml
Wraps Invoke-CIJob.ps1 for reuse across repos. Inputs: build-command,
artifact-source, submodules, guest-os, use-git-clone, configuration,
template-path, snapshot-name, artifact-name, artifact-retention-days.
Anti-injection: all params via INPUT_* env vars, never interpolated in shell.
§6.4 - Build matrix windows+linux in build-nsis.yml
gitea/workflows/build-nsis.yml rewritten with strategy.matrix target:
[windows, linux], fail-fast: false, routes to {target}-build runner.
Added action inputs: job-id-suffix (matrix disambig for artifact dirs),
repo-url (SSH URL override for gitea-ci alias).
§6.5 - ExtraGuestEnv secret injection chain
New param -ExtraGuestEnv [hashtable] in Invoke-CIJob.ps1 and
Invoke-RemoteBuild.ps1. Windows: SetEnvironmentVariable in WinRM session
ScriptBlock before build command. Linux: export KEY='val'; prefix before
cd && buildcmd (POSIX single-quote escaping). Composite action input
extra-guest-env-json (JSON object -> ConvertFrom-Json -> hashtable),
never logged.
PSScriptAnalyzer fixes: _Common.psm1, Invoke-RetentionPolicy.ps1,
Watch-RunnerHealth.ps1 (empty catch, ShouldProcess suppression, etc.)
Docs/test: TEST-PLAN-v1.3-to-HEAD.md updated §3.3/§6.1/§5.4 status.
TODO.md: §6 fully closed (6.1-6.2 done, 6.3 deferred, 6.4-6.5 done, 6.6 done).
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
# .gitea/actions/local-ci-build/action.yml
|
||||
#
|
||||
# Reusable composite action — Local CI/CD System
|
||||
#
|
||||
# Wraps Invoke-CIJob.ps1 (ephemeral VMware VM pipeline) into a single callable
|
||||
# step, usable from any Gitea repository without duplicating the orchestration
|
||||
# logic.
|
||||
#
|
||||
# Stored in: Simone/local-ci-system / .gitea/actions/local-ci-build/action.yml
|
||||
#
|
||||
# Usage from a calling repo's workflow:
|
||||
#
|
||||
# jobs:
|
||||
# build:
|
||||
# runs-on: windows-build # or linux-build
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main
|
||||
# with:
|
||||
# build-command: 'python build_plugin.py --final --dist-dir dist'
|
||||
# artifact-source: 'dist'
|
||||
# submodules: 'true'
|
||||
#
|
||||
# Runner label determines the guest OS template:
|
||||
# windows-build -> WinBuild2025 (WinRM/HTTPS transport)
|
||||
# linux-build -> LinuxBuild2404 (SSH transport)
|
||||
|
||||
name: Local CI Build
|
||||
description: >
|
||||
Builds a project using an ephemeral VMware VM managed by Invoke-CIJob.ps1.
|
||||
Handles the full VM lifecycle: clone template -> start VM -> run build ->
|
||||
collect artifacts -> destroy VM.
|
||||
Compatible with windows-build (WinRM) and linux-build (SSH) runners.
|
||||
|
||||
inputs:
|
||||
build-command:
|
||||
description: >
|
||||
Shell command to execute inside the build VM.
|
||||
Example: 'python build_plugin.py --final --dist-dir dist'
|
||||
required: true
|
||||
|
||||
artifact-source:
|
||||
description: >
|
||||
Path inside VM where build outputs are located, relative to the CI work
|
||||
directory. Collected to F:\CI\Artifacts\{JobId}\ on the host.
|
||||
required: false
|
||||
default: 'dist'
|
||||
|
||||
submodules:
|
||||
description: >
|
||||
Set to "true" to clone the repository with --recurse-submodules.
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
guest-os:
|
||||
description: >
|
||||
Guest OS type: Windows, Linux, or Auto.
|
||||
Auto (default) detects the OS from the VMX guestOS field after cloning.
|
||||
required: false
|
||||
default: 'Auto'
|
||||
|
||||
use-git-clone:
|
||||
description: >
|
||||
Set to "true" to clone the repository inside the VM rather than
|
||||
transferring a zip from the host. Requires Git in the template (see
|
||||
Setup-WinBuild2025.ps1 Step 12 / Setup-LinuxBuild2404.sh).
|
||||
Preferred for repositories larger than ~200 MB.
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
configuration:
|
||||
description: >
|
||||
Build configuration string forwarded to the build command (Release/Debug).
|
||||
required: false
|
||||
default: 'Release'
|
||||
|
||||
template-path:
|
||||
description: >
|
||||
Override the VMX template path. Leave empty (default) to use the value
|
||||
from the GITEA_CI_TEMPLATE_PATH runner environment variable set in
|
||||
runner/config.yaml.
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
snapshot-name:
|
||||
description: >
|
||||
Override the snapshot name to clone from. Leave empty (default) to use
|
||||
GITEA_CI_SNAPSHOT_NAME runner env var, or "BaseClean" if that is also
|
||||
unset.
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
artifact-name:
|
||||
description: >
|
||||
Name for the artifact uploaded to Gitea. Leave empty to use the default:
|
||||
build-{run_id}-{sha}.
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
artifact-retention-days:
|
||||
description: 'Days to retain uploaded artifacts in Gitea.'
|
||||
required: false
|
||||
default: '14'
|
||||
|
||||
job-id-suffix:
|
||||
description: >
|
||||
Optional suffix appended to the job ID: run_id-run_attempt-SUFFIX.
|
||||
Use to disambiguate parallel matrix legs so each leg writes to its own
|
||||
artifact directory (e.g. set to matrix.target: windows, linux).
|
||||
Leave empty (default) for single-job workflows.
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
repo-url:
|
||||
description: >
|
||||
Override the repository URL passed to Invoke-CIJob.ps1.
|
||||
Defaults to github.server_url/repository.git (HTTPS).
|
||||
Set to an SSH URL (e.g. ssh://gitea-ci/Org/repo.git) when the host
|
||||
runner uses an SSH alias for Gitea.
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
extra-guest-env-json:
|
||||
description: >
|
||||
JSON object of extra environment variables to inject into the build VM
|
||||
guest before the build command runs. Use for signing keys and secrets.
|
||||
Example: '{"SIGN_PASS":"${{ secrets.SIGN_PASS }}","GPG_KEY_ID":"0xABCD1234"}'
|
||||
Values are passed via ExtraGuestEnv hashtable in Invoke-CIJob.ps1 and
|
||||
are never echoed to the log.
|
||||
required: false
|
||||
default: '{}'
|
||||
artifact-path:
|
||||
description: >
|
||||
Host-side directory where Invoke-CIJob.ps1 placed collected artifacts.
|
||||
Format: F:\CI\Artifacts\{run_id}-{run_attempt}
|
||||
value: ${{ steps.invoke-ci.outputs.artifact-path }}
|
||||
|
||||
artifact-name:
|
||||
description: 'Name used when uploading the artifact to Gitea.'
|
||||
value: ${{ steps.invoke-ci.outputs.artifact-name }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
|
||||
# ── Invoke the CI orchestrator ─────────────────────────────────────────────
|
||||
- name: Invoke CI Job
|
||||
id: invoke-ci
|
||||
shell: powershell
|
||||
# Inputs are forwarded via environment variables to avoid shell injection
|
||||
# from user-supplied build-command or path strings.
|
||||
env:
|
||||
INPUT_BUILD_COMMAND: ${{ inputs.build-command }}
|
||||
INPUT_ARTIFACT_SOURCE: ${{ inputs.artifact-source }}
|
||||
INPUT_SUBMODULES: ${{ inputs.submodules }}
|
||||
INPUT_GUEST_OS: ${{ inputs.guest-os }}
|
||||
INPUT_USE_GIT_CLONE: ${{ inputs.use-git-clone }}
|
||||
INPUT_CONFIGURATION: ${{ inputs.configuration }}
|
||||
INPUT_TEMPLATE_PATH: ${{ inputs.template-path }}
|
||||
INPUT_SNAPSHOT_NAME: ${{ inputs.snapshot-name }}
|
||||
INPUT_ARTIFACT_NAME: ${{ inputs.artifact-name }}
|
||||
INPUT_JOB_ID_SUFFIX: ${{ inputs.job-id-suffix }}
|
||||
INPUT_REPO_URL: ${{ inputs.repo-url }}
|
||||
INPUT_EXTRA_GUEST_ENV_JSON: ${{ inputs.extra-guest-env-json }}
|
||||
run: |
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ciScriptsDir = 'N:\Code\Workspace\Local-CI-CD-System\scripts'
|
||||
|
||||
# Build job ID — append suffix when provided (matrix disambiguation)
|
||||
$jobId = '${{ github.run_id }}-${{ github.run_attempt }}'
|
||||
if ($env:INPUT_JOB_ID_SUFFIX) { $jobId = "$jobId-$($env:INPUT_JOB_ID_SUFFIX)" }
|
||||
$artifactPath = "F:\CI\Artifacts\$jobId"
|
||||
|
||||
# Resolve artifact name: caller-supplied or default to build-{run_id}-{sha}
|
||||
if ($env:INPUT_ARTIFACT_NAME) {
|
||||
$artifactName = $env:INPUT_ARTIFACT_NAME
|
||||
}
|
||||
else {
|
||||
$artifactName = "build-${{ github.run_id }}-${{ github.sha }}"
|
||||
}
|
||||
|
||||
# Resolve repository URL: explicit override or construct from GitHub context
|
||||
if ($env:INPUT_REPO_URL) {
|
||||
$repoUrl = $env:INPUT_REPO_URL
|
||||
}
|
||||
else {
|
||||
$repoUrl = '${{ github.server_url }}/${{ github.repository }}.git'
|
||||
}
|
||||
|
||||
# Parse extra-guest-env-json into a hashtable (§6.5 secret injection)
|
||||
$extraGuestEnv = @{}
|
||||
if ($env:INPUT_EXTRA_GUEST_ENV_JSON -and $env:INPUT_EXTRA_GUEST_ENV_JSON -ne '{}') {
|
||||
$parsed = $env:INPUT_EXTRA_GUEST_ENV_JSON | ConvertFrom-Json
|
||||
foreach ($prop in $parsed.PSObject.Properties) {
|
||||
$extraGuestEnv[$prop.Name] = [string]$prop.Value
|
||||
}
|
||||
}
|
||||
|
||||
# Build parameter hashtable for Invoke-CIJob.ps1
|
||||
$params = @{
|
||||
JobId = $jobId
|
||||
RepoUrl = $repoUrl
|
||||
Branch = '${{ github.ref_name }}'
|
||||
Commit = '${{ github.sha }}'
|
||||
BuildCommand = $env:INPUT_BUILD_COMMAND
|
||||
GuestArtifactSource = $env:INPUT_ARTIFACT_SOURCE
|
||||
GuestOS = $env:INPUT_GUEST_OS
|
||||
Configuration = $env:INPUT_CONFIGURATION
|
||||
}
|
||||
|
||||
if ($env:INPUT_SUBMODULES -eq 'true') { $params['Submodules'] = $true }
|
||||
if ($env:INPUT_USE_GIT_CLONE -eq 'true') { $params['UseGitClone'] = $true }
|
||||
if ($env:INPUT_TEMPLATE_PATH) { $params['TemplatePath'] = $env:INPUT_TEMPLATE_PATH }
|
||||
if ($env:INPUT_SNAPSHOT_NAME) { $params['SnapshotName'] = $env:INPUT_SNAPSHOT_NAME }
|
||||
if ($extraGuestEnv.Count -gt 0) { $params['ExtraGuestEnv'] = $extraGuestEnv }
|
||||
|
||||
& "$ciScriptsDir\Invoke-CIJob.ps1" @params
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
# Emit outputs consumed by subsequent steps
|
||||
"artifact-path=$artifactPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||
"artifact-name=$artifactName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||
|
||||
# ── Upload build artifacts on success ─────────────────────────────────────
|
||||
- name: Upload artifacts
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.invoke-ci.outputs.artifact-name }}
|
||||
path: ${{ steps.invoke-ci.outputs.artifact-path }}\
|
||||
retention-days: ${{ inputs.artifact-retention-days }}
|
||||
if-no-files-found: error
|
||||
|
||||
# ── Upload diagnostic logs on failure ─────────────────────────────────────
|
||||
- name: Upload diagnostic logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-${{ github.sha }}
|
||||
path: ${{ steps.invoke-ci.outputs.artifact-path }}\*.log
|
||||
retention-days: 3
|
||||
if-no-files-found: ignore
|
||||
@@ -166,3 +166,52 @@ jobs:
|
||||
# path: F:\CI\Artifacts\${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.target }}\
|
||||
# retention-days: 14
|
||||
# if-no-files-found: error
|
||||
|
||||
---
|
||||
# COMPOSITE ACTION VARIANT (recommended for new repositories)
|
||||
# Uses the reusable composite action stored in the CI system repository.
|
||||
# This variant replaces the verbose inline shell block with a single `uses:` step.
|
||||
# The composite action handles: Invoke-CIJob.ps1 call + artifact upload + log upload.
|
||||
#
|
||||
# Reference: gitea/actions/local-ci-build/action.yml in this repository.
|
||||
# Deployed path in Gitea: Simone/local-ci-system / .gitea/actions/local-ci-build/action.yml
|
||||
#
|
||||
# Minimum example (Windows build, Python project with submodules):
|
||||
#
|
||||
# name: Build (Composite)
|
||||
#
|
||||
# on:
|
||||
# push:
|
||||
# tags:
|
||||
# - 'v*'
|
||||
# workflow_dispatch:
|
||||
#
|
||||
# jobs:
|
||||
# build:
|
||||
# runs-on: windows-build
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main
|
||||
# with:
|
||||
# build-command: 'python build_plugin.py --final --dist-dir dist'
|
||||
# artifact-source: 'dist'
|
||||
# submodules: 'true'
|
||||
#
|
||||
# Cross-platform matrix variant using the composite action:
|
||||
#
|
||||
# jobs:
|
||||
# build:
|
||||
# strategy:
|
||||
# matrix:
|
||||
# target: [windows, linux]
|
||||
# fail-fast: false
|
||||
# runs-on: ${{ matrix.target }}-build
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main
|
||||
# with:
|
||||
# build-command: 'python build_plugin.py --final --dist-dir dist'
|
||||
# artifact-source: 'dist'
|
||||
# submodules: 'true'
|
||||
# guest-os: 'Auto'
|
||||
# artifact-name: 'build-${{ matrix.target }}-${{ github.sha }}'
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
# Build (Local CI) — pipeline per nsis-plugin-nsinnounp
|
||||
#
|
||||
# Eseguito dal runner locale windows-build su WS1-W11.
|
||||
# L'orchestratore (Invoke-CIJob.ps1) gira sull'host e:
|
||||
# 1. Clona il repo sull'host (con submoduli)
|
||||
# 2. Crea una VM efimera da snapshot BaseClean
|
||||
# 3. Copia il sorgente nella VM via WinRM (zip transfer)
|
||||
# 4. Esegue python build_plugin.py nella VM
|
||||
# 5. Raccoglie artifacts.zip sull'host
|
||||
# 6. Distrugge la VM
|
||||
# Cross-platform matrix build: runs in parallel on both windows-build (WinRM)
|
||||
# and linux-build (SSH) runners using the reusable composite action.
|
||||
# Uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main
|
||||
#
|
||||
# Trigger: push su tag v*, oppure run manuale
|
||||
|
||||
@@ -21,26 +16,23 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-build
|
||||
strategy:
|
||||
matrix:
|
||||
target: [windows, linux]
|
||||
fail-fast: false
|
||||
|
||||
# Runner label must match config.yaml labels: windows-build or linux-build
|
||||
runs-on: ${{ matrix.target }}-build
|
||||
|
||||
steps:
|
||||
- name: Run CI Job
|
||||
shell: powershell
|
||||
run: |
|
||||
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
|
||||
-JobId '${{ github.run_id }}' `
|
||||
-RepoUrl 'ssh://gitea-ci/Simone/nsis-plugin-nsinnounp.git' `
|
||||
-Branch '${{ github.ref_name }}' `
|
||||
-Commit '${{ github.sha }}' `
|
||||
-TemplatePath $env:GITEA_CI_TEMPLATE_PATH `
|
||||
-Submodules `
|
||||
-BuildCommand 'python build_plugin.py --final --dist-dir dist' `
|
||||
-GuestArtifactSource 'dist'
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main
|
||||
with:
|
||||
name: nsis-plugin-nsinnounp-${{ github.ref_name }}
|
||||
path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip
|
||||
if-no-files-found: error
|
||||
build-command: 'python build_plugin.py --final --dist-dir dist'
|
||||
artifact-source: 'dist'
|
||||
submodules: 'true'
|
||||
# SSH URL alias configured in host ~/.ssh/config — same for both runners
|
||||
repo-url: 'ssh://gitea-ci/Simone/nsis-plugin-nsinnounp.git'
|
||||
# Suffix disambiguates artifact dirs: F:\CI\Artifacts\{run_id}-{attempt}-windows
|
||||
job-id-suffix: '${{ matrix.target }}'
|
||||
artifact-name: 'nsis-plugin-nsinnounp-${{ matrix.target }}-${{ github.ref_name }}'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user