Skip to content

Commit 473ccb7

Browse files
Update common Docker engineering infrastructure with latest
1 parent 8fe3441 commit 473ccb7

17 files changed

Lines changed: 348 additions & 49 deletions

eng/docker-tools/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All breaking changes and new features in `eng/docker-tools` will be documented i
44

55
---
66

7+
## 2026-03-04: Pre-build validation gated by `preBuildTestScriptPath` variable
8+
9+
The `PreBuildValidation` job condition now checks the new `preBuildTestScriptPath` variable instead of `testScriptPath`.
10+
This allows repos to independently control whether pre-build validation runs, without affecting functional tests.
11+
12+
The new variable defaults to `$(testScriptPath)`, so existing repos that have pre-build tests are not affected.
13+
Repos that do not have pre-build tests can set `preBuildTestScriptPath` to `""` to skip the job entirely.
14+
15+
---
16+
717
## 2026-02-19: Separate Registry Endpoints from Authentication
818

919
- Pull request: [#1945](https://github.com/dotnet/docker-tools/pull/1945)

eng/docker-tools/DEV-GUIDE.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,16 @@ The `stages` variable is a comma-separated string that controls which pipeline s
180180
```yaml
181181
variables:
182182
- name: stages
183-
value: "build,test,publish" # Run all stages
183+
value: "build,test,sign,publish" # Run all stages
184184
```
185185
186186
Common patterns:
187-
- `"build"` - Build only, no tests or publishing
188-
- `"build,test"` - Build and test, but don't publish
187+
- `"build"` - Build only, no tests, signing, or publishing
188+
- `"build,test"` - Build and test, but don't sign or publish
189+
- `"build,test,sign"` - Build, test, and sign, but don't publish
190+
- `"sign"` - Sign only (when re-running failed signing from a previous build)
189191
- `"publish"` - Publish only (when re-running a failed publish from a previous build)
190-
- `"build,test,publish"` - Full pipeline
192+
- `"build,test,sign,publish"` - Full pipeline
191193
192194
**Note:** The `Post_Build` stage is implicitly included whenever `build` is in the stages list. You don't need to specify it separately—it automatically runs after Build to merge image info files and consolidate SBOMs.
193195

@@ -372,11 +374,13 @@ Note: For simple retries of failed jobs, use the Azure Pipelines UI "Re-run fail
372374

373375
| Scenario | stages | sourceBuildPipelineRunId |
374376
|----------|--------|--------------------------|
375-
| Normal full build | `"build,test,publish"` | `$(Build.BuildId)` (default) |
377+
| Normal full build | `"build,test,sign,publish"` | `$(Build.BuildId)` (default) |
376378
| Re-run publish after infra fix | `"publish"` | ID of the successful build run |
377379
| Re-test after infra fix | `"test"` | ID of the build run to test |
380+
| Re-sign after infra fix | `"sign"` | ID of the build run to sign |
378381
| Build only (no publish) | `"build"` | `$(Build.BuildId)` (default) |
379382
| Test + publish (skip build) | `"test,publish"` | ID of the build run |
383+
| Sign + publish (skip build/test) | `"sign,publish"` | ID of the build run |
380384

381385
**In the Azure DevOps UI:**
382386

eng/docker-tools/templates/jobs/post-build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ parameters:
33
internalProjectName: null
44
publicProjectName: null
55
customInitSteps: []
6+
publishConfig: null
67

78
jobs:
89
- job: Build
@@ -18,6 +19,7 @@ jobs:
1819
parameters:
1920
dockerClientOS: linux
2021
customInitSteps: ${{ parameters.customInitSteps }}
22+
publishConfig: ${{ parameters.publishConfig }}
2123
- template: /eng/docker-tools/templates/steps/download-build-artifact.yml@self
2224
parameters:
2325
targetPath: $(Build.ArtifactStagingDirectory)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Signs container images using ESRP/Notary v2.
2+
# This job downloads the merged image-info artifact and signs all images listed in it.
3+
parameters:
4+
pool: {}
5+
internalProjectName: null
6+
publicProjectName: null
7+
customInitSteps: []
8+
publishConfig: null
9+
sourceBuildPipelineRunId: ""
10+
11+
jobs:
12+
- job: Sign
13+
pool: ${{ parameters.pool }}
14+
variables:
15+
imageInfoDir: $(Build.ArtifactStagingDirectory)/image-info
16+
steps:
17+
18+
# Install MicroBuild signing plugin for ESRP container image signing
19+
- template: /eng/docker-tools/templates/steps/init-signing-linux.yml@self
20+
parameters:
21+
signType: ${{ parameters.publishConfig.Signing.SignType }}
22+
envFileVariableName: signingEnvFilePath
23+
24+
# Setup docker and ImageBuilder
25+
- template: /eng/docker-tools/templates/steps/init-common.yml@self
26+
parameters:
27+
dockerClientOS: linux
28+
setupImageBuilder: true
29+
customInitSteps: ${{ parameters.customInitSteps }}
30+
publishConfig: ${{ parameters.publishConfig }}
31+
envFilePath: $(signingEnvFilePath)
32+
33+
# Download merged image-info artifact from Post_Build stage (or from a previous pipeline run)
34+
- template: /eng/docker-tools/templates/steps/download-build-artifact.yml@self
35+
parameters:
36+
targetPath: $(imageInfoDir)
37+
artifactName: image-info
38+
pipelineRunId: ${{ parameters.sourceBuildPipelineRunId }}
39+
40+
- template: /eng/docker-tools/templates/steps/run-imagebuilder.yml@self
41+
parameters:
42+
displayName: 🔏 Sign Container Images
43+
internalProjectName: ${{ parameters.internalProjectName }}
44+
args: >-
45+
signImages
46+
$(artifactsPath)/image-info/image-info.json
47+
--registry-override ${{ parameters.publishConfig.BuildRegistry.server }}
48+
--repo-prefix ${{ parameters.publishConfig.BuildRegistry.repoPrefix }}
49+
50+
- template: /eng/docker-tools/templates/steps/run-imagebuilder.yml@self
51+
parameters:
52+
displayName: ✅ Verify Container Image Signatures
53+
internalProjectName: ${{ parameters.internalProjectName }}
54+
args: >-
55+
verifySignatures
56+
$(artifactsPath)/image-info/image-info.json
57+
--registry-override ${{ parameters.publishConfig.BuildRegistry.server }}
58+
--repo-prefix ${{ parameters.publishConfig.BuildRegistry.repoPrefix }}

eng/docker-tools/templates/jobs/test-images-linux-client.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
strategy:
1818
matrix: $[ ${{ parameters.matrix }} ]
1919
${{ if eq(parameters.preBuildValidation, 'true') }}:
20-
condition: and(succeeded(), ne(variables.testScriptPath, ''))
20+
condition: and(succeeded(), ne(variables.preBuildTestScriptPath, ''))
2121
pool: ${{ parameters.pool }}
2222
timeoutInMinutes: ${{ parameters.testJobTimeout }}
2323
steps:

eng/docker-tools/templates/stages/build-and-test.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,33 @@ stages:
220220
internalProjectName: ${{ parameters.internalProjectName }}
221221
publicProjectName: ${{ parameters.publicProjectName }}
222222
customInitSteps: ${{ parameters.customInitSteps }}
223+
publishConfig: ${{ parameters.publishConfig }}
224+
225+
################################################################################
226+
# Sign Images
227+
################################################################################
228+
- ${{ if eq(parameters.publishConfig.Signing.Enabled, true) }}:
229+
- stage: Sign
230+
dependsOn: Post_Build
231+
condition: "
232+
and(
233+
ne(stageDependencies.Post_Build.outputs['Build.MergeImageInfoFiles.noImageInfos'], 'true'),
234+
and(
235+
contains(variables['stages'], 'sign'),
236+
or(
237+
and(
238+
succeeded(),
239+
contains(variables['stages'], 'build')),
240+
not(contains(variables['stages'], 'build')))))"
241+
jobs:
242+
- template: /eng/docker-tools/templates/jobs/sign-images.yml@self
243+
parameters:
244+
pool: ${{ parameters.linuxAmd64Pool }}
245+
internalProjectName: ${{ parameters.internalProjectName }}
246+
publicProjectName: ${{ parameters.publicProjectName }}
247+
customInitSteps: ${{ parameters.customInitSteps }}
248+
publishConfig: ${{ parameters.publishConfig }}
249+
sourceBuildPipelineRunId: ${{ parameters.sourceBuildPipelineRunId }}
223250

224251
################################################################################
225252
# Test Images

eng/docker-tools/templates/stages/dotnet/publish-config-nonprod.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ parameters:
3838
type: object
3939
default: {}
4040

41+
# Enable container image signing
42+
- name: enableSigning
43+
type: boolean
44+
default: false
45+
4146

4247
stages:
4348
- template: ${{ parameters.stagesTemplate }}
@@ -103,3 +108,12 @@ stages:
103108
id: $(test-nonprod.serviceConnection.id)
104109
clientId: $(test-nonprod.serviceConnection.clientId)
105110
tenantId: $(testTenant)
111+
112+
Signing:
113+
Enabled: ${{ parameters.enableSigning }}
114+
ImageSigningKeyCode: $(microBuildSigningKeyCode.testing)
115+
ReferrerSigningKeyCode: $(microBuildSigningKeyCode.testing)
116+
# Use signType 'real' even for non-prod to actually sign with the test certificate.
117+
# The 'test' signType skips signing entirely on linux; the test keycode provides a non-production certificate.
118+
SignType: real
119+
TrustStoreName: test

eng/docker-tools/templates/stages/dotnet/publish-config-prod.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ parameters:
3838
type: object
3939
default: {}
4040

41+
# Enable container image signing
42+
- name: enableSigning
43+
type: boolean
44+
default: false
45+
4146

4247
stages:
4348
- template: ${{ parameters.stagesTemplate }}
@@ -103,3 +108,10 @@ stages:
103108
id: $(test.serviceConnection.id)
104109
clientId: $(test.serviceConnection.clientId)
105110
tenantId: $(test.serviceConnection.tenantId)
111+
112+
Signing:
113+
Enabled: ${{ parameters.enableSigning }}
114+
ImageSigningKeyCode: $(microBuildSigningKeyCode.containers)
115+
ReferrerSigningKeyCode: $(microBuildSigningKeyCode.attestations)
116+
SignType: real
117+
TrustStoreName: supplychain

eng/docker-tools/templates/stages/publish.yml

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,39 +33,36 @@ stages:
3333
${{ if eq(parameters.isStandalonePublish, true) }}:
3434
dependsOn: []
3535
${{ else }}:
36-
${{ if and(eq(variables['System.TeamProject'], parameters.internalProjectName), ne(variables['Build.Reason'], 'PullRequest')) }}:
37-
dependsOn: Test
38-
${{ else }}:
39-
dependsOn: Post_Build
36+
dependsOn:
37+
- ${{ if eq(parameters.publishConfig.Signing.Enabled, true) }}:
38+
- Sign
39+
- ${{ if and(eq(variables['System.TeamProject'], parameters.internalProjectName), ne(variables['Build.Reason'], 'PullRequest')) }}:
40+
- Test
41+
- ${{ else }}:
42+
- Post_Build
43+
# Run when all of the following are true:
44+
# 1. The pipeline has not been canceled.
45+
# 2. The stages variable includes 'publish'.
46+
# 3. Either signing is not enabled, or the Sign stage succeeded.
47+
# 4. Either the stages variable does not include 'build', or Post_Build succeeded.
48+
# 5. Either the stages variable does not include 'test', or Test succeeded/was skipped.
4049
condition: "
4150
and(
4251
not(canceled()),
43-
and(
44-
contains(variables['stages'], 'publish'),
45-
or(
46-
or(
47-
and(
48-
and(
49-
contains(variables['stages'], 'build'),
50-
succeeded('Post_Build')),
51-
and(
52-
contains(variables['stages'], 'test'),
53-
in(dependencies.Test.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'))),
54-
or(
55-
and(
56-
not(contains(variables['stages'], 'build')),
57-
and(
58-
contains(variables['stages'], 'test'),
59-
in(dependencies.Test.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'))),
60-
and(
61-
not(contains(variables['stages'], 'test')),
62-
and(
63-
contains(variables['stages'], 'build'),
64-
succeeded('Post_Build'))))),
65-
not(
66-
or(
67-
contains(variables['stages'], 'build'),
68-
contains(variables['stages'], 'test'))))))"
52+
contains(variables['stages'], 'publish'),
53+
or(
54+
ne(lower('${{ parameters.publishConfig.Signing.Enabled }}'), 'true'),
55+
in(dependencies.Sign.result, 'Succeeded', 'SucceededWithIssues')
56+
),
57+
or(
58+
not(contains(variables['stages'], 'build')),
59+
succeeded('Post_Build')
60+
),
61+
or(
62+
not(contains(variables['stages'], 'test')),
63+
in(dependencies.Test.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
64+
)
65+
)"
6966
jobs:
7067
- template: /eng/docker-tools/templates/jobs/publish.yml@self
7168
parameters:

eng/docker-tools/templates/steps/generate-appsettings.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
11
# .NET Microsoft.Extensions.Configuration reads appsettings.json from the working directory
22
# where ImageBuilder is run. Place it in the repo root so it will be available at runtime.
33
parameters:
4+
# See:
5+
# - publish-config-prod.yml
6+
# - publish-config-nonprod.yml
7+
# - PublishConfiguration.cs
48
- name: publishConfig
59
type: object
10+
# This should be the path to $(Build.ArtifactStagingDirectory). It is parameterized
11+
# here since it is mounted into the ImageBuilder container at runtime.
12+
- name: artifactStagingDirectory
13+
type: string
14+
default: ""
615
- name: condition
716
type: string
817
default: "true"
918

1019
steps:
1120
- powershell: |-
21+
# Escape backslashes for JSON compatibility (Windows paths like D:\a\_work become D:\\a\\_work)
22+
$artifactStagingDirectory = "${{ parameters.artifactStagingDirectory }}" -replace '\\', '\\'
1223
$appsettingsJsonContent = @"
1324
{
14-
"PublishConfiguration": ${{ convertToJson(parameters.publishConfig) }}
25+
"PublishConfiguration": ${{ convertToJson(parameters.publishConfig) }},
26+
"BuildConfiguration": {
27+
"ArtifactStagingDirectory": "$artifactStagingDirectory"
28+
}
1529
}
1630
"@
1731
Set-Content -Path "appsettings.json" -Value $appsettingsJsonContent

0 commit comments

Comments
 (0)