Skip to content

Commit 930716e

Browse files
gfraiteurclaude
andcommitted
DockerBuild: auto-select Dockerfile.win2022 for Windows Server 2022 hosts
Also: TeamCity uses matches for Windows agent requirement Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 360cb58 commit 930716e

File tree

4 files changed

+328
-22
lines changed

4 files changed

+328
-22
lines changed

DockerBuild.ps1

Lines changed: 113 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,35 @@ function Get-TimestampFile
420420
return $timestampFile
421421
}
422422

423+
function Get-ContentHash {
424+
param(
425+
[string]$DockerfilePath,
426+
[string]$ContextDirectory
427+
)
428+
429+
$hashInput = Get-Content $DockerfilePath -Raw -ErrorAction SilentlyContinue
430+
if (-not $hashInput) { $hashInput = "" }
431+
432+
# Add context files (excluding generated .g/ directory)
433+
$contextFiles = Get-ChildItem $ContextDirectory -Recurse -File -ErrorAction SilentlyContinue |
434+
Where-Object { $_.FullName -notmatch '[/\\]\.g[/\\]' } |
435+
Sort-Object FullName
436+
437+
foreach ($file in $contextFiles) {
438+
$content = Get-Content $file.FullName -Raw -ErrorAction SilentlyContinue
439+
if ($content) {
440+
$hashInput += "`n--- $($file.Name) ---`n"
441+
$hashInput += $content
442+
}
443+
}
444+
445+
$hashBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash(
446+
[System.Text.Encoding]::UTF8.GetBytes($hashInput)
447+
)
448+
# Use 8 bytes (16 hex chars) for uniqueness
449+
return [System.BitConverter]::ToString($hashBytes, 0, 8).Replace("-", "").ToLower()
450+
}
451+
423452
# Dictionary to track volume mounts with "writable wins" logic
424453
$script:VolumeMountDict = @{}
425454

@@ -464,6 +493,22 @@ if (-not $Dockerfile)
464493
{
465494
$Dockerfile = "Dockerfile"
466495
}
496+
497+
# On Windows, detect if we're running on Server 2022 (build < 26100) and use matching Dockerfile
498+
# Windows Server 2025 is build 26100+, 2022 is build 20348-26099
499+
if ($IsWindows -and -not $Claude)
500+
{
501+
$osBuild = [System.Environment]::OSVersion.Version.Build
502+
if ($osBuild -lt 26100)
503+
{
504+
$win2022Dockerfile = "Dockerfile.win2022"
505+
if (Test-Path (Join-Path $PSScriptRoot $win2022Dockerfile))
506+
{
507+
Write-Host "Detected Windows Server 2022 (build $osBuild), using $win2022Dockerfile" -ForegroundColor Cyan
508+
$Dockerfile = $win2022Dockerfile
509+
}
510+
}
511+
}
467512
}
468513

469514
# Get the full path of the Dockerfile
@@ -476,19 +521,21 @@ else
476521
$dockerfileFullPath = Join-Path $PSScriptRoot $Dockerfile
477522
}
478523

479-
# Generate a hash of the Dockerfile full path (4 bytes, 8 hex chars)
480-
$hashBytes = (New-Object -TypeName System.Security.Cryptography.SHA256Managed).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($dockerfileFullPath))
481-
$dockerfileHash = [System.BitConverter]::ToString($hashBytes, 0, 4).Replace("-", "").ToLower()
524+
# Generate content-based hash for image tag
525+
$contentHash = Get-ContentHash -DockerfilePath $dockerfileFullPath -ContextDirectory $dockerContextDirectory
526+
$ghcrRegistry = $env:GHCR_REGISTRY
482527

483-
# Generate ImageTag using the hash
484-
if ( [string]::IsNullOrEmpty($ImageName))
485-
{
486-
$ImageTag = "dockerfile-$dockerfileHash"
487-
Write-Host "Generated image tag from Dockerfile path hash: $ImageTag" -ForegroundColor Cyan
528+
if ($ghcrRegistry) {
529+
# GHCR mode: use registry URL with content hash
530+
$ImageTag = "${ghcrRegistry}:${contentHash}"
531+
Write-Host "GHCR image tag: $ImageTag" -ForegroundColor Cyan
488532
}
489-
else
490-
{
491-
$ImageTag = "$ImageName`:$dockerfileHash"
533+
elseif ([string]::IsNullOrEmpty($ImageName)) {
534+
$ImageTag = "dockerfile-$contentHash"
535+
Write-Host "Generated image tag from content hash: $ImageTag" -ForegroundColor Cyan
536+
}
537+
else {
538+
$ImageTag = "$ImageName`:$contentHash"
492539
Write-Host "Image will be tagged as: $ImageTag" -ForegroundColor Cyan
493540
}
494541

@@ -1148,6 +1195,47 @@ if (-not $existingContainerId)
11481195
}
11491196
}
11501197

1198+
# GHCR authentication and pull logic
1199+
$builtNewImage = $false
1200+
$dockerConfigArg = @()
1201+
1202+
if ($ghcrRegistry -and -not $NoBuildImage -and -not $existingContainerId) {
1203+
# Extract registry host from full URL (e.g., ghcr.io from ghcr.io/owner/repo)
1204+
$registryHost = ($ghcrRegistry -split '/')[0]
1205+
1206+
# Create a temporary Docker config directory to avoid credential helper issues
1207+
# (e.g., docker-credential-desktop not found when using Docker Engine without Desktop)
1208+
$tempDockerConfig = Join-Path $env:TEMP "docker-config-$(New-Guid)"
1209+
New-Item -ItemType Directory -Path $tempDockerConfig -Force | Out-Null
1210+
@{ auths = @{} } | ConvertTo-Json | Set-Content (Join-Path $tempDockerConfig "config.json")
1211+
$dockerConfigArg = @("--config", $tempDockerConfig)
1212+
1213+
# Authenticate to GHCR
1214+
$ghcrToken = $env:GHCR_TOKEN
1215+
if ($ghcrToken) {
1216+
Write-Host "Authenticating to GHCR..." -ForegroundColor Gray
1217+
# GHCR accepts any username when using a PAT, commonly 'github' is used
1218+
$ghcrToken | docker @dockerConfigArg login $registryHost --username github --password-stdin 2>$null
1219+
if ($LASTEXITCODE -ne 0) {
1220+
Write-Host "Warning: GHCR authentication failed. Pull/push may fail." -ForegroundColor Yellow
1221+
}
1222+
}
1223+
else {
1224+
Write-Host "Warning: GHCR_TOKEN not set. GHCR pull/push may fail." -ForegroundColor Yellow
1225+
}
1226+
1227+
# Try to pull the image
1228+
Write-Host "Checking GHCR for existing image: $ImageTag" -ForegroundColor Cyan
1229+
docker @dockerConfigArg pull $ImageTag 2>$null
1230+
if ($LASTEXITCODE -eq 0) {
1231+
Write-Host "Using cached image from GHCR." -ForegroundColor Green
1232+
$NoBuildImage = $true
1233+
}
1234+
else {
1235+
Write-Host "Image not found in GHCR, will build locally." -ForegroundColor Yellow
1236+
}
1237+
}
1238+
11511239
# Building the image.
11521240
if (-not $NoBuildImage -and -not $existingContainerId)
11531241
{
@@ -1221,6 +1309,20 @@ RUN if [ -n "`$MOUNTPOINTS" ]; then \
12211309
Write-Host "Docker build failed with exit code $LASTEXITCODE" -ForegroundColor Red
12221310
exit $LASTEXITCODE
12231311
}
1312+
1313+
$builtNewImage = $true
1314+
1315+
# Auto-push to GHCR after successful build
1316+
if ($ghcrRegistry) {
1317+
Write-Host "Pushing image to GHCR: $ImageTag" -ForegroundColor Cyan
1318+
docker @dockerConfigArg push $ImageTag
1319+
if ($LASTEXITCODE -ne 0) {
1320+
Write-Host "Warning: Failed to push image to GHCR" -ForegroundColor Yellow
1321+
}
1322+
else {
1323+
Write-Host "Successfully pushed to GHCR" -ForegroundColor Green
1324+
}
1325+
}
12241326
}
12251327
else
12261328
{

Dockerfile.win2022

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# escape=`
2+
3+
# This file is auto-generated by PostSharp.Engineering.
4+
5+
FROM mcr.microsoft.com/windows/servercore:ltsc2022
6+
7+
# The initial shell is Windows PowerShell (use full path to avoid HCS issues)
8+
SHELL ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-Command"]
9+
10+
# Prepare environment
11+
ENV PSExecutionPolicyPreference=Bypass
12+
ENV POWERSHELL_UPDATECHECK=Off
13+
ENV TEMP=C:\Temp
14+
ENV TMP=C:\Temp
15+
ENV RUNNING_IN_DOCKER=TRUE
16+
17+
# Set locale for consistent behavior regardless of host locale
18+
ENV LANG=C.UTF-8
19+
ENV LC_ALL=C.UTF-8
20+
ENV DOTNET_CLI_UI_LANGUAGE=en
21+
ENV VSLANG=1033
22+
23+
# Set base PATH explicitly to avoid issues with expansion
24+
ENV PATH="C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0"
25+
26+
# Enable long path support
27+
RUN Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
28+
29+
30+
31+
# Install Git
32+
RUN Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/v2.50.0.windows.1/PortableGit-2.50.0-64-bit.7z.exe -OutFile PortableGit.exe; `
33+
Start-Process -FilePath .\PortableGit.exe -ArgumentList '-o"C:\git"', '-y' -Wait; `
34+
Remove-Item PortableGit.exe
35+
36+
# Add git to PATH using ENV directive (persists across shell switches)
37+
ENV PATH="C:\git\cmd;C:\git\bin;C:\git\usr\bin;${PATH}"
38+
39+
RUN git config --system core.longpaths true
40+
41+
# Set CLAUDE_CODE_GIT_BASH_PATH for Claude Code
42+
ENV CLAUDE_CODE_GIT_BASH_PATH=C:\git\bin\bash.exe
43+
44+
45+
# Install PowerShell 7
46+
RUN Invoke-WebRequest -Uri https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/PowerShell-7.5.2-win-x64.msi -OutFile PowerShell.msi; `
47+
$process = Start-Process msiexec.exe -Wait -PassThru -ArgumentList '/I PowerShell.msi /quiet'; `
48+
if ($process.ExitCode -ne 0) { exit $process.ExitCode }; `
49+
Remove-Item PowerShell.msi
50+
51+
ENV PATH="C:\Program Files\PowerShell\7;${PATH}"
52+
53+
54+
# Download .NET Installer
55+
RUN Invoke-WebRequest -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1
56+
57+
# Add .NET to PATH using ENV directive (persists across shell switches)
58+
ENV PATH="C:\Program Files\dotnet;${PATH}"
59+
60+
61+
# Install .NET Sdk 9.0.310
62+
RUN & .\dotnet-install.ps1 -Version 9.0.310 -InstallDir 'C:\Program Files\dotnet'
63+
64+
65+
# Epilogue
66+
# Link to private repository for GHCR visibility
67+
LABEL org.opencontainers.image.source=https://github.com/postsharp/PostSharp.Engineering.Images
68+
69+
# Create docker-context directory for build scripts
70+
RUN New-Item -ItemType Directory -Path c:\docker-context -Force | Out-Null
71+
72+
# Create directories for mountpoints
73+
ARG MOUNTPOINTS
74+
RUN if ($env:MOUNTPOINTS) { `
75+
$mounts = $env:MOUNTPOINTS -split ';'; `
76+
foreach ($dir in $mounts) { `
77+
if ($dir) { `
78+
Write-Host "Creating directory $dir`."; `
79+
New-Item -ItemType Directory -Path $dir -Force | Out-Null; `
80+
} `
81+
} `
82+
}
83+
84+
# Import environment variables
85+
COPY ReadEnvironmentVariables.ps1 c:\docker-context\ReadEnvironmentVariables.ps1
86+
COPY .g/env.g.json c:\docker-context\env.g.json
87+
RUN c:\docker-context\ReadEnvironmentVariables.ps1 c:\docker-context\env.g.json
88+
89+
# Copy Init.g.ps1 placeholder (drive mappings handled inline in docker run)
90+
COPY .g/Init.g.ps1 c:\docker-context\Init.g.ps1
91+
92+
# Configure .NET SDK
93+
ENV DOTNET_NOLOGO=1
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace PostSharp.Engineering.BuildTools.Docker;
2+
3+
public enum ContainerOperatingSystem
4+
{
5+
Windows2025,
6+
Default = Windows2025,
7+
Windows2022,
8+
Linux
9+
}

0 commit comments

Comments
 (0)