# .gitea/workflows/module-publish.yml # Per-module NuGet and container publishing to Gitea registry # Sprint: SPRINT_20251226_004_CICD name: Module Publish on: workflow_dispatch: inputs: module: description: 'Module to publish' required: true type: choice options: - Authority - Attestor - Concelier - Scanner - Policy - Signer - Excititor - Gateway - Scheduler - Orchestrator - TaskRunner - Notify - CLI version: description: 'Semantic version (e.g., 1.2.3)' required: true type: string publish_nuget: description: 'Publish NuGet packages' type: boolean default: true publish_container: description: 'Publish container image' type: boolean default: true prerelease: description: 'Mark as prerelease' type: boolean default: false push: tags: - 'module-*-v*' # e.g., module-authority-v1.2.3 env: DOTNET_VERSION: '10.0.100' DOTNET_NOLOGO: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 REGISTRY: git.stella-ops.org NUGET_SOURCE: https://git.stella-ops.org/api/packages/stella-ops.org/nuget/index.json jobs: # =========================================================================== # PARSE TAG (for tag-triggered builds) # =========================================================================== parse-tag: name: Parse Tag runs-on: ubuntu-22.04 if: github.event_name == 'push' outputs: module: ${{ steps.parse.outputs.module }} version: ${{ steps.parse.outputs.version }} steps: - name: Parse module and version from tag id: parse run: | TAG="${{ github.ref_name }}" # Expected format: module-{name}-v{version} # Example: module-authority-v1.2.3 if [[ "$TAG" =~ ^module-([a-zA-Z]+)-v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then MODULE="${BASH_REMATCH[1]}" VERSION="${BASH_REMATCH[2]}" # Capitalize first letter MODULE="$(echo "${MODULE:0:1}" | tr '[:lower:]' '[:upper:]')${MODULE:1}" echo "module=$MODULE" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Parsed: module=$MODULE, version=$VERSION" else echo "::error::Invalid tag format. Expected: module-{name}-v{version}" exit 1 fi # =========================================================================== # VALIDATE # =========================================================================== validate: name: Validate Inputs runs-on: ubuntu-22.04 needs: [parse-tag] if: always() && (needs.parse-tag.result == 'success' || needs.parse-tag.result == 'skipped') outputs: module: ${{ steps.resolve.outputs.module }} version: ${{ steps.resolve.outputs.version }} publish_nuget: ${{ steps.resolve.outputs.publish_nuget }} publish_container: ${{ steps.resolve.outputs.publish_container }} steps: - name: Resolve inputs id: resolve run: | if [[ "${{ github.event_name }}" == "push" ]]; then MODULE="${{ needs.parse-tag.outputs.module }}" VERSION="${{ needs.parse-tag.outputs.version }}" PUBLISH_NUGET="true" PUBLISH_CONTAINER="true" else MODULE="${{ github.event.inputs.module }}" VERSION="${{ github.event.inputs.version }}" PUBLISH_NUGET="${{ github.event.inputs.publish_nuget }}" PUBLISH_CONTAINER="${{ github.event.inputs.publish_container }}" fi echo "module=$MODULE" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "publish_nuget=$PUBLISH_NUGET" >> "$GITHUB_OUTPUT" echo "publish_container=$PUBLISH_CONTAINER" >> "$GITHUB_OUTPUT" echo "=== Resolved Configuration ===" echo "Module: $MODULE" echo "Version: $VERSION" echo "Publish NuGet: $PUBLISH_NUGET" echo "Publish Container: $PUBLISH_CONTAINER" - name: Validate version format run: | VERSION="${{ steps.resolve.outputs.version }}" if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then echo "::error::Invalid version format. Expected: MAJOR.MINOR.PATCH[-prerelease]" exit 1 fi # =========================================================================== # PUBLISH NUGET # =========================================================================== publish-nuget: name: Publish NuGet runs-on: ubuntu-22.04 needs: [validate] if: needs.validate.outputs.publish_nuget == 'true' steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - name: Determine project path id: path run: | MODULE="${{ needs.validate.outputs.module }}" # Map module names to project paths case "$MODULE" in Authority) PROJECT="src/Authority/StellaOps.Authority.WebService/StellaOps.Authority.WebService.csproj" ;; Attestor) PROJECT="src/Attestor/StellaOps.Attestor.WebService/StellaOps.Attestor.WebService.csproj" ;; Concelier) PROJECT="src/Concelier/StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj" ;; Scanner) PROJECT="src/Scanner/StellaOps.Scanner.WebService/StellaOps.Scanner.WebService.csproj" ;; Policy) PROJECT="src/Policy/StellaOps.Policy.Gateway/StellaOps.Policy.Gateway.csproj" ;; Signer) PROJECT="src/Signer/StellaOps.Signer.WebService/StellaOps.Signer.WebService.csproj" ;; Excititor) PROJECT="src/Excititor/StellaOps.Excititor.WebService/StellaOps.Excititor.WebService.csproj" ;; Gateway) PROJECT="src/Gateway/StellaOps.Gateway.WebService/StellaOps.Gateway.WebService.csproj" ;; Scheduler) PROJECT="src/Scheduler/StellaOps.Scheduler.WebService/StellaOps.Scheduler.WebService.csproj" ;; Orchestrator) PROJECT="src/Orchestrator/StellaOps.Orchestrator.WebService/StellaOps.Orchestrator.WebService.csproj" ;; TaskRunner) PROJECT="src/TaskRunner/StellaOps.TaskRunner.WebService/StellaOps.TaskRunner.WebService.csproj" ;; Notify) PROJECT="src/Notify/StellaOps.Notify.WebService/StellaOps.Notify.WebService.csproj" ;; CLI) PROJECT="src/Cli/StellaOps.Cli/StellaOps.Cli.csproj" ;; *) echo "::error::Unknown module: $MODULE" exit 1 ;; esac echo "project=$PROJECT" >> "$GITHUB_OUTPUT" echo "Project path: $PROJECT" - name: Restore dependencies run: dotnet restore ${{ steps.path.outputs.project }} - name: Build run: | dotnet build ${{ steps.path.outputs.project }} \ --configuration Release \ --no-restore \ -p:Version=${{ needs.validate.outputs.version }} - name: Pack NuGet run: | dotnet pack ${{ steps.path.outputs.project }} \ --configuration Release \ --no-build \ -p:Version=${{ needs.validate.outputs.version }} \ -p:PackageVersion=${{ needs.validate.outputs.version }} \ --output out/packages - name: Push to Gitea NuGet registry run: | for nupkg in out/packages/*.nupkg; do echo "Pushing: $nupkg" dotnet nuget push "$nupkg" \ --source "${{ env.NUGET_SOURCE }}" \ --api-key "${{ secrets.GITEA_TOKEN }}" \ --skip-duplicate done - name: Upload NuGet artifacts uses: actions/upload-artifact@v4 with: name: nuget-${{ needs.validate.outputs.module }}-${{ needs.validate.outputs.version }} path: out/packages/*.nupkg retention-days: 30 # =========================================================================== # PUBLISH CONTAINER # =========================================================================== publish-container: name: Publish Container runs-on: ubuntu-22.04 needs: [validate] if: needs.validate.outputs.publish_container == 'true' && needs.validate.outputs.module != 'CLI' steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITEA_TOKEN }} - name: Determine image name id: image run: | MODULE="${{ needs.validate.outputs.module }}" VERSION="${{ needs.validate.outputs.version }}" MODULE_LOWER=$(echo "$MODULE" | tr '[:upper:]' '[:lower:]') IMAGE="${{ env.REGISTRY }}/stella-ops.org/${MODULE_LOWER}" echo "name=$IMAGE" >> "$GITHUB_OUTPUT" echo "tag_version=${IMAGE}:${VERSION}" >> "$GITHUB_OUTPUT" echo "tag_latest=${IMAGE}:latest" >> "$GITHUB_OUTPUT" echo "Image: $IMAGE" echo "Tags: ${VERSION}, latest" - name: Build and push container uses: docker/build-push-action@v5 with: context: . file: devops/docker/Dockerfile.platform target: ${{ needs.validate.outputs.module | lower }} push: true tags: | ${{ steps.image.outputs.tag_version }} ${{ steps.image.outputs.tag_latest }} cache-from: type=gha cache-to: type=gha,mode=max labels: | org.opencontainers.image.title=StellaOps ${{ needs.validate.outputs.module }} org.opencontainers.image.version=${{ needs.validate.outputs.version }} org.opencontainers.image.source=https://git.stella-ops.org/stella-ops.org/git.stella-ops.org org.opencontainers.image.revision=${{ github.sha }} # =========================================================================== # PUBLISH CLI BINARIES (multi-platform) # =========================================================================== publish-cli: name: Publish CLI (${{ matrix.runtime }}) runs-on: ubuntu-22.04 needs: [validate] if: needs.validate.outputs.module == 'CLI' strategy: matrix: runtime: - linux-x64 - linux-arm64 - win-x64 - osx-x64 - osx-arm64 steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - name: Install cross-compilation tools if: matrix.runtime == 'linux-arm64' run: | sudo apt-get update sudo apt-get install -y --no-install-recommends binutils-aarch64-linux-gnu - name: Publish CLI run: | dotnet publish src/Cli/StellaOps.Cli/StellaOps.Cli.csproj \ --configuration Release \ --runtime ${{ matrix.runtime }} \ --self-contained true \ -p:Version=${{ needs.validate.outputs.version }} \ -p:PublishSingleFile=true \ -p:PublishTrimmed=true \ -p:EnableCompressionInSingleFile=true \ --output out/cli/${{ matrix.runtime }} - name: Create archive run: | VERSION="${{ needs.validate.outputs.version }}" RUNTIME="${{ matrix.runtime }}" cd out/cli/$RUNTIME if [[ "$RUNTIME" == win-* ]]; then zip -r ../stellaops-cli-${VERSION}-${RUNTIME}.zip . else tar -czvf ../stellaops-cli-${VERSION}-${RUNTIME}.tar.gz . fi - name: Upload CLI artifacts uses: actions/upload-artifact@v4 with: name: cli-${{ needs.validate.outputs.version }}-${{ matrix.runtime }} path: | out/cli/*.zip out/cli/*.tar.gz retention-days: 30 # =========================================================================== # SUMMARY # =========================================================================== summary: name: Publish Summary runs-on: ubuntu-22.04 needs: [validate, publish-nuget, publish-container, publish-cli] if: always() steps: - name: Generate Summary run: | echo "## Module Publish Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| Module | ${{ needs.validate.outputs.module }} |" >> $GITHUB_STEP_SUMMARY echo "| Version | ${{ needs.validate.outputs.version }} |" >> $GITHUB_STEP_SUMMARY echo "| NuGet | ${{ needs.publish-nuget.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Container | ${{ needs.publish-container.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| CLI | ${{ needs.publish-cli.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Registry URLs" >> $GITHUB_STEP_SUMMARY echo "- NuGet: \`${{ env.NUGET_SOURCE }}\`" >> $GITHUB_STEP_SUMMARY echo "- Container: \`${{ env.REGISTRY }}/stella-ops.org/${{ needs.validate.outputs.module | lower }}\`" >> $GITHUB_STEP_SUMMARY - name: Check for failures if: contains(needs.*.result, 'failure') run: | echo "::error::One or more publish jobs failed" exit 1