-
Notifications
You must be signed in to change notification settings - Fork 3.6k
380 lines (335 loc) Β· 14.4 KB
/
Copy pathinfra-container-windows.yml
File metadata and controls
380 lines (335 loc) Β· 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
name: 'π§ Infra Β· π³ Container (Windows)'
# =============================================================================
# π³ Windows Container β Build and push the Windows Quarto build container
# =============================================================================
#
# Builds the Windows Docker image using native Docker (not Buildx, which has
# compatibility issues with Windows containers on GitHub Actions).
#
# Flow:
# 1. CHECKOUT & LOGIN β Authenticate to GHCR
# 2. BUILD β Native Docker build with optional cache bypass
# 3. PUSH β Push image to GHCR and extract digest
# 4. SUMMARY β Report build status and image metadata
#
# Triggers:
# - push (dev): Changes to book/tools/dependencies/ or book/docker/windows/
# - schedule: Weekly rebuild (Sunday 2am UTC, after Linux container)
# - workflow_dispatch: Manual with force_rebuild, no_cache options
# - workflow_call: Reusable by other workflows
#
# Deploys to: ghcr.io/harvard-edge/mlsysbook/quarto-windows:latest
# Secrets: GITHUB_TOKEN (automatic)
# Vars: BOOK_DOCKER
#
# Related:
# - infra-container-linux.yml β Linux container build (runs 2h before)
# - infra-health-check.yml β Daily validation of built containers
# - infra-cleanup-caches.yml β Weekly GHA cache cleanup
#
# =============================================================================
# Prevent multiple builds running simultaneously
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
inputs:
force_rebuild:
description: 'Force rebuild even if no changes'
required: false
default: false
type: boolean
no_cache:
description: 'Disable Docker build cache (fresh build)'
required: false
default: false
type: boolean
container_registry:
description: 'Container registry URL'
required: false
default: 'ghcr.io'
type: string
container_name:
description: 'Container image name'
required: false
default: 'quarto-windows'
type: string
container_tag:
description: 'Container tag'
required: false
default: 'latest'
type: string
workflow_call:
inputs:
force_rebuild:
required: false
default: false
type: boolean
no_cache:
required: false
default: false
type: boolean
container_registry:
required: false
default: 'ghcr.io'
type: string
container_name:
required: false
default: 'quarto-windows'
type: string
container_tag:
required: false
default: 'latest'
type: string
outputs:
build-status:
description: "Container build status (success/failure/skipped)"
value: ${{ jobs.build.outputs.build-status }}
image-name:
description: "Full container image name with registry"
value: ${{ jobs.build.outputs.image-name }}
image-digest:
description: "Container image digest (SHA256)"
value: ${{ jobs.build.outputs.image-digest }}
cache-hit:
description: "Whether build used cache (true/false)"
value: ${{ jobs.build.outputs.cache-hit }}
# Re-enable automatic triggers
schedule:
- cron: '0 2 * * 0' # Weekly rebuild (Sunday at 2am - after Linux container)
push:
branches: [dev] # Only trigger on dev branch, not main
paths:
- 'book/tools/dependencies/**'
- 'book/docker/windows/**'
- '.github/workflows/book-build-windows-container.yml'
env:
# =============================================================================
# PATH CONFIGURATION - Uses GitHub Repository Variables (Settings > Variables)
# =============================================================================
# MLSysBook content lives under book/ to accommodate TinyTorch at root
# Use ${{ vars.BOOK_ROOT }}, ${{ vars.BOOK_DOCKER }}, etc. in workflow steps
# Variables: BOOK_ROOT, BOOK_DOCKER, BOOK_TOOLS, BOOK_QUARTO, BOOK_DEPS
# Container Registry Configuration (configurable via inputs)
REGISTRY: ${{ (github.event_name == 'workflow_dispatch' && inputs.container_registry) || 'ghcr.io' }}
IMAGE_NAME: ${{ github.repository }}/${{ (github.event_name == 'workflow_dispatch' && inputs.container_name) || 'quarto-windows' }}
CONTAINER_TAG: ${{ (github.event_name == 'workflow_dispatch' && inputs.container_tag) || 'latest' }}
# Container Build Configuration
# Using vars.BOOK_DOCKER (repository variable) - works in all contexts
DOCKERFILE_PATH: ./${{ vars.BOOK_DOCKER }}/windows/Dockerfile
CONTEXT_PATH: .
jobs:
build:
runs-on: windows-latest
if: github.repository_owner == 'harvard-edge'
timeout-minutes: 180 # takes about 2 hours to build on Windows
permissions:
contents: read
packages: write
outputs:
build-status: ${{ steps.build.outputs.build-status }}
image-name: ${{ steps.build.outputs.image-name }}
image-digest: ${{ steps.build.outputs.image-digest }}
cache-hit: ${{ steps.build.outputs.cache-hit }}
steps:
- name: π₯ Checkout repository
uses: actions/checkout@v6
# Skip Docker Buildx for Windows containers - use native Docker engine
# Buildx doesn't properly support Windows containers on GitHub Actions
# - name: π οΈ Set up Docker Buildx
# uses: docker/setup-buildx-action@v4
- name: π Log in to Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: π§Ή Free runner disk space
shell: pwsh
run: |
Write-Host "π Disk space BEFORE cleanup:"
Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | Format-Table Name, @{N='Used(GB)';E={[math]::Round($_.Used/1GB,1)}}, @{N='Free(GB)';E={[math]::Round($_.Free/1GB,1)}}
# Pre-installed toolchains and SDKs not used by the Quarto Windows container build.
# NOTE: Do NOT remove anything from C:\ProgramData\chocolatey (scoop uses choco fallback),
# do NOT touch C:\Users\runneradmin\scoop (scoop state for the build),
# and do NOT touch Docker binaries or Windows container images already pulled.
$paths = @(
# Original toolchain removals
"C:\hostedtoolcache",
"C:\mingw64",
"C:\ghcup",
"C:\cabal",
"C:\Strawberry",
"$env:LOCALAPPDATA\Temp\*",
# Additional pre-installed SDKs the Quarto build does not use
"C:\Android",
"$env:ANDROID_HOME",
"$env:ANDROID_SDK_ROOT",
"C:\Program Files\Android",
"C:\Program Files (x86)\Android",
"C:\Program Files\Go",
"C:\Program Files\Microsoft\jdk-*",
"C:\Program Files\Java",
"C:\Program Files\dotnet\sdk\*",
"C:\Program Files\Unity Hub",
"C:\Program Files (x86)\Windows Kits\10\ExtensionSDKs",
"C:\tools\php*",
"C:\tools\ruby*",
"C:\Julia",
"C:\Miniconda",
"C:\Rust",
"C:\Users\runneradmin\.cargo",
"C:\Users\runneradmin\.rustup",
"C:\vcpkg",
"C:\SeleniumWebDrivers",
# Package manager caches that the Quarto build does not consume
"$env:LOCALAPPDATA\NuGet\Cache",
"$env:LOCALAPPDATA\NuGet\v3-cache",
"$env:APPDATA\npm-cache",
"$env:LOCALAPPDATA\pip\Cache",
"$env:LOCALAPPDATA\ChocolateyHttpCache",
# Windows update download cache (re-fetched on demand if needed)
"C:\Windows\SoftwareDistribution\Download\*"
)
foreach ($p in $paths) {
if ($p -and (Test-Path $p)) {
Write-Host "Removing $p ..."
Remove-Item -Recurse -Force $p -ErrorAction SilentlyContinue
}
}
# Empty recycle bin in case prior cleanup sent large dirs there
try { Clear-RecycleBin -Force -ErrorAction SilentlyContinue } catch {}
# Prune any stale Docker data (safe: no image has been built yet this run)
docker system prune -af 2>$null
docker builder prune -af 2>$null
docker volume prune -f 2>$null
Write-Host "π Disk space AFTER cleanup:"
Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | Format-Table Name, @{N='Used(GB)';E={[math]::Round($_.Used/1GB,1)}}, @{N='Free(GB)';E={[math]::Round($_.Free/1GB,1)}}
- name: π·οΈ Extract metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ env.CONTAINER_TAG }}
- name: π Allow non-distributable layers to reach the registry
shell: pwsh
run: |
# The image is built FROM mcr.microsoft.com/windows/server, whose base
# layers are marked non-distributable. Docker refuses to upload those
# by default, so the manifest lands at GHCR referencing blobs the
# registry never received and the push ends in "unknown blob" after a
# two-hour build. Opting in for this registry uploads them instead.
$configDir = "C:\ProgramData\Docker\config"
$configPath = Join-Path $configDir "daemon.json"
New-Item -ItemType Directory -Force -Path $configDir | Out-Null
if (Test-Path $configPath) {
$config = Get-Content $configPath -Raw | ConvertFrom-Json
} else {
$config = [PSCustomObject]@{}
}
$registries = @("${{ env.REGISTRY }}")
$config | Add-Member -NotePropertyName "allow-nondistributable-artifacts" `
-NotePropertyValue $registries -Force
$config | ConvertTo-Json -Depth 10 | Set-Content $configPath -Encoding utf8
Write-Host "π daemon.json:"
Get-Content $configPath | Write-Host
Restart-Service docker
$deadline = (Get-Date).AddMinutes(2)
while ((Get-Date) -lt $deadline) {
docker info --format '{{.ServerVersion}}' 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) { break }
Start-Sleep -Seconds 3
}
if ($LASTEXITCODE -ne 0) {
Write-Host "β Docker daemon did not come back after restart"
exit 1
}
Write-Host "β
Docker daemon restarted with $($registries -join ', ') allowed"
- name: π³ Build and Push Windows container
id: build
shell: pwsh
# Use native Docker instead of book/docker/build-push-action for Windows containers
# Buildx has compatibility issues with Windows containers on GitHub Actions
run: |
# Extract image name and tag from metadata
$IMAGE_TAG = "${{ steps.meta.outputs.tags }}"
$NO_CACHE = "${{ github.event_name == 'workflow_dispatch' && inputs.no_cache || false }}"
Write-Host "π¨ Building Windows container..."
Write-Host "π Image: $IMAGE_TAG"
Write-Host "π Context: ${{ env.CONTEXT_PATH }}"
Write-Host "π Dockerfile: ${{ env.DOCKERFILE_PATH }}"
Write-Host "π No Cache: $NO_CACHE"
# Build the container using native Docker
$buildArgs = @(
"build",
"--file", "${{ env.DOCKERFILE_PATH }}",
"--tag", $IMAGE_TAG
)
# Add no-cache flag if requested
if ($NO_CACHE -eq "true") {
$buildArgs += "--no-cache"
Write-Host "π« Cache disabled - building from scratch"
} else {
Write-Host "πΎ Using Docker cache"
}
# Add labels from metadata
$labels = "${{ steps.meta.outputs.labels }}"
if ($labels) {
$labels -split "`n" | ForEach-Object {
if ($_.Trim()) {
$buildArgs += "--label", $_.Trim()
}
}
}
# Add context path
$buildArgs += "${{ env.CONTEXT_PATH }}"
Write-Host "π¨ Running: docker $($buildArgs -join ' ')"
& docker @buildArgs
if ($LASTEXITCODE -ne 0) {
Write-Host "β Docker build failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
Write-Host "β
Build completed successfully"
# Push the container
Write-Host "π€ Pushing container to registry..."
& docker push $IMAGE_TAG
if ($LASTEXITCODE -ne 0) {
Write-Host "β Docker push failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
Write-Host "β
Push completed successfully"
# Get image digest for output
$DIGEST = & docker inspect --format='{{index .RepoDigests 0}}' $IMAGE_TAG 2>$null
if ($DIGEST -match '@(.+)$') {
$DIGEST = $matches[1]
} else {
$DIGEST = "unknown"
}
# Set outputs for build summary
"digest=$DIGEST" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"cache-hit=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: π Build Summary
id: build-summary
if: always()
shell: pwsh
run: |
# Determine build status
if ("${{ steps.build.outcome }}" -eq "success") {
$BUILD_STATUS = "success"
} else {
$BUILD_STATUS = "failure"
}
# Extract build information
$IMAGE_NAME = "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.CONTAINER_TAG }}"
$IMAGE_DIGEST = "${{ steps.build.outputs.digest }}"
$CACHE_HIT = "${{ steps.build.outputs.cache-hit }}"
"build-status=$BUILD_STATUS" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"image-name=$IMAGE_NAME" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"image-digest=$IMAGE_DIGEST" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"cache-hit=$CACHE_HIT" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
Write-Host "π Build Status: $BUILD_STATUS"
Write-Host "π³ Image: $IMAGE_NAME"
Write-Host "π Digest: $IMAGE_DIGEST"
Write-Host "πΎ Cache Hit: $CACHE_HIT"