diff --git a/.github/workflows/ado/package-build.yml b/.github/workflows/ado/package-build.yml index 823f3eaee24..abbd1b437aa 100644 --- a/.github/workflows/ado/package-build.yml +++ b/.github/workflows/ado/package-build.yml @@ -8,11 +8,14 @@ # .github/workflows/ado/templates/package-build-stages.yml # # Authenticates via Workload Identity Federation (OIDC) and calls the Control -# Tower APIs to run the v1 post-merge delta build: +# Tower APIs to run the v1 post-merge delta build against each configured +# deployment: # 1. Resolve the (target, source) commit range for this push from the # previous CI build (ADO Builds API). # 2. Submit official package builds for the components that changed across -# that range, via Workload Identity Federation (OIDC) to Control Tower. +# that range, via deployment-specific Workload Identity Federation (OIDC) +# service connections to Control Tower. Deployment jobs run independently +# in parallel. # # The setup + change-detection + validation steps are composed from the shared # templates/steps/* step templates via templates/steps/get-changes-info.yml. @@ -23,28 +26,30 @@ # - scripts/ci/components/ - (cross-pipeline azldev helpers shared with the GH Actions PR gates). # # Prerequisites (ADO / Azure Portal): -# 1. Entra ID App Registration with audience URI +# 1. Entra ID App Registrations with audience URIs # "api://" (see variable group below). -# 2. Federated identity credential on the app registration for the ADO -# service connection (issuer: https://vstoken.dev.azure.com/, +# 2. Federated identity credentials on the app registrations for the ADO +# service connections (issuer: https://vstoken.dev.azure.com/, # subject: sc:////). -# 3. ARM service connection in ADO project settings using Workload Identity -# Federation (manual). +# 3. DEV and PROD ARM service connections in ADO project settings using +# Workload Identity Federation (manual). # 4. CI trigger configured (in ADO pipeline settings) to fire on pushes to # the target branch. # # Variable Group (ADO Pipelines > Library): # Name: "ControlTower-PRCheck" # Required variables: -# - ApiAudienceDev : DEV Entra ID audience URI -# - ApiBaseAFDUrlDev : DEV Azure Front Door endpoint base URL +# - ApiAudienceDev : DEV Entra ID audience URI +# - ApiAudienceProd : PROD Entra ID audience URI +# - ApiBaseAFDUrlDev : DEV Azure Front Door endpoint base URL +# - ApiBaseAFDUrlProd : PROD Azure Front Door endpoint base URL # Trigger controlled by ADO branch policy — not YAML triggers. trigger: none pr: none -# Variables extracted from this group: ApiAudienceDev, ApiBaseAFDUrlDev. +# Variables extracted from this group: deployment audiences and endpoint URLs. variables: - group: ControlTower-PRCheck @@ -78,15 +83,21 @@ extends: stages: - template: /.github/workflows/ado/templates/package-build-stages.yml@self parameters: - apiAudience: $(ApiAudienceDev) - apiBaseAFDUrl: $(ApiBaseAFDUrlDev) artifactBaseName: packagebuild containerImage: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + deployments: + DEV: + apiAudience: $(ApiAudienceDev) + apiBaseAFDUrl: $(ApiBaseAFDUrlDev) + serviceConnection: CT-Endpoints-Access-ServiceConnection-DEV + PROD: + apiAudience: $(ApiAudienceProd) + apiBaseAFDUrl: $(ApiBaseAFDUrlProd) + serviceConnection: CT-Endpoints-Access-ServiceConnection-PROD outputDirectory: $(Build.ArtifactStagingDirectory)/output # Distro-config RPM build-environment for the 4.0 branch. packageEnvironment: "4.0" poolType: linux - serviceConnection: CT-Endpoints-Access-ServiceConnection-DEV # Must exceed the script's --poll-timeout-seconds (default 600s = 10m) # with enough headroom for setup steps and the final API call. timeoutInMinutes: 25 diff --git a/.github/workflows/ado/templates/package-build-stages.yml b/.github/workflows/ado/templates/package-build-stages.yml index a60eeb4991a..468046ac7a6 100644 --- a/.github/workflows/ado/templates/package-build-stages.yml +++ b/.github/workflows/ado/templates/package-build-stages.yml @@ -6,19 +6,21 @@ # OneBranch-coupled knobs as parameters. The wrapper at # .github/workflows/ado/package-build.yml supplies concrete values. # -# Per push: gather the change-set info (get-changes-info, mode=postmerge: full -# history, deps, commit range, lock + render verification, changed-component -# set), then submit an official Control Tower build for the changed components. +# Per push and deployment: gather the change-set info (get-changes-info, +# mode=postmerge: full history, deps, commit range, lock + render verification, +# changed-component set), then submit an official Control Tower build for the +# changed components. Each deployment runs in a separate parallel job. parameters: - - name: apiAudience - type: string - - name: apiBaseAFDUrl - type: string - name: artifactBaseName type: string - name: containerImage type: string + # Named map of deployment identifier to apiAudience, apiBaseAFDUrl, and + # serviceConnection. Identifiers must be valid Azure Pipelines job-name + # suffixes. + - name: deployments + type: object - name: outputDirectory type: string # Distro-config RPM build-environment used by Control Tower. @@ -27,70 +29,70 @@ parameters: - name: poolType type: string default: linux - - name: serviceConnection - type: string - name: timeoutInMinutes type: number stages: - stage: PackageBuild jobs: - - job: PackageBuild - # Post-merge build: code has already merged, so there is no PR or merge - # queue to gate. Fails loud -- any failing step turns the run red so - # submission/validation breakage is visible rather than masked. The - # build runs asynchronously in Control Tower/Koji; this pipeline only - # submits it and confirms acceptance (see run_official_package_build.py). - timeoutInMinutes: ${{ parameters.timeoutInMinutes }} - pool: - type: ${{ parameters.poolType }} - variables: - - name: LinuxContainerImage - value: ${{ parameters.containerImage }} - - name: ob_artifactBaseName - value: ${{ parameters.artifactBaseName }} - - name: ob_outputDirectory - value: ${{ parameters.outputDirectory }} - - name: packageEnvironment - value: ${{ parameters.packageEnvironment }} - steps: - # Sets job variables: - # - changedComponentsFile - - template: steps/get-changes-info.yml - parameters: - mode: postmerge + - ${{ each deployment in parameters.deployments }}: + - job: PackageBuild_${{ deployment.key }} + displayName: "Package build (${{ deployment.key }})" + # Post-merge build: code has already merged, so there is no PR or + # merge queue to gate. Fails loud -- any failing deployment turns + # the run red so submission/validation breakage is visible rather + # than masked. Deployment jobs run independently in parallel. The + # build runs asynchronously in Control Tower/Koji; this pipeline + # only submits it and confirms acceptance (see + # run_official_package_build.py). + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + pool: + type: ${{ parameters.poolType }} + variables: + - name: LinuxContainerImage + value: ${{ parameters.containerImage }} + - name: ob_artifactBaseName + value: ${{ parameters.artifactBaseName }}-${{ deployment.key }} + - name: ob_outputDirectory + value: ${{ parameters.outputDirectory }}/${{ deployment.key }} + - name: packageEnvironment + value: ${{ parameters.packageEnvironment }} + steps: + # Sets job variables: + # - changedComponentsFile + - template: steps/get-changes-info.yml + parameters: + mode: postmerge - - template: steps/install-deps.yml - parameters: - installControlTowerClient: true + - template: steps/install-deps.yml + parameters: + installControlTowerClient: true - - task: AzureCLI@2 - displayName: "Submit official package build to Control Tower" - inputs: - azureSubscription: ${{ parameters.serviceConnection }} - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - set -euo pipefail + - task: AzureCLI@2 + displayName: "Submit official package build to Control Tower (${{ deployment.key }})" + inputs: + azureSubscription: ${{ deployment.value.serviceConnection }} + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + set -euo pipefail - python3 scripts/ci/control-tower/run_official_package_build.py \ - --api-audience "$API_AUDIENCE" \ - --api-base-url "$API_BASE_URL" \ - --build-reason "$BUILD_REASON" \ - --changed-components-file "$CHANGED_COMPONENTS_FILE" \ - --environment "$PACKAGE_ENVIRONMENT" - env: - # apiAudience / apiBaseAFDUrl come from the parameters directly - # (not surfaced as same-named job variables): the wrapper passes - # $(ApiAudience) / $(ApiBaseAFDUrl) from the variable group, and a - # job variable of the same (case-insensitive) name would - # self-reference -> "cyclical reference" and never resolve. - API_AUDIENCE: ${{ parameters.apiAudience }} - API_BASE_URL: ${{ parameters.apiBaseAFDUrl }} - CHANGED_COMPONENTS_FILE: $(changedComponentsFile) - PACKAGE_ENVIRONMENT: $(packageEnvironment) - # Secret predefined variables are not auto-exposed. - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - # Build.Reason is predefined and auto-exposed as $BUILD_REASON; - # reference it directly and do not map it here (env: overrides - # of reserved variables are silently ignored by the agent). + python3 scripts/ci/control-tower/run_official_package_build.py \ + --api-audience "$API_AUDIENCE" \ + --api-base-url "$API_BASE_URL" \ + --build-reason "$BUILD_REASON" \ + --changed-components-file "$CHANGED_COMPONENTS_FILE" \ + --environment "$PACKAGE_ENVIRONMENT" + env: + # Values come directly from parameters rather than same-named + # job variables, which would create cyclical references. + API_AUDIENCE: ${{ deployment.value.apiAudience }} + API_BASE_URL: ${{ deployment.value.apiBaseAFDUrl }} + CHANGED_COMPONENTS_FILE: $(changedComponentsFile) + PACKAGE_ENVIRONMENT: $(packageEnvironment) + # Secret predefined variables are not auto-exposed. + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + # Build.Reason is predefined and auto-exposed as + # $BUILD_REASON; reference it directly and do not map it here + # (env: overrides of reserved variables are silently ignored + # by the agent).