Skip to content

Commit cf9fc1d

Browse files
committed
fix(container): reconcile update state on restart
Add `Sync-CippContainerUpdateState` and use it in both timer and container management endpoints so stale "update available" results are corrected after a restart. The update-check flow now also captures and preserves `RemoteBuildDate` and `CheckedTag`, and the settings/status response includes build-date metadata for both running and remote images.
1 parent e569d2f commit cf9fc1d

3 files changed

Lines changed: 87 additions & 9 deletions

File tree

Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-ContainerUpdateCheck.ps1

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ function Start-ContainerUpdateCheck {
1212

1313
if ($PSCmdlet.ShouldProcess('Start-ContainerUpdateCheck', 'Check for container image updates')) {
1414
$SettingsTable = Get-CippTable -tablename 'ContainerUpdateSettings'
15-
$Settings = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1
15+
# Reconcile the stored check result with the running build first — a restart may
16+
# have applied a previously detected update, and this must clear the stale
17+
# "update available" flag even when checks are disabled or not yet due.
18+
$Settings = Sync-CippContainerUpdateState
1619

1720
if (-not $Settings -or $Settings.CheckInterval -eq '0' -or [string]::IsNullOrWhiteSpace($Settings.CheckInterval)) {
1821
Write-Information 'Container update check: disabled or not configured'
@@ -153,10 +156,12 @@ function Start-ContainerUpdateCheck {
153156
}
154157

155158
$RemoteVersion = $manifest.annotations.'org.opencontainers.image.version'
156-
if (-not $RemoteVersion -and $manifest.config.digest) {
159+
$RemoteBuildDate = $manifest.annotations.'org.opencontainers.image.created'
160+
if ((-not $RemoteVersion -or -not $RemoteBuildDate) -and $manifest.config.digest) {
157161
try {
158162
$config = Invoke-RestMethod -Uri "https://ghcr.io/v2/$imagePath/blobs/$($manifest.config.digest)" -Method GET -Headers $authHeader -ErrorAction Stop
159-
$RemoteVersion = $config.config.Labels.'org.opencontainers.image.version'
163+
if (-not $RemoteVersion) { $RemoteVersion = $config.config.Labels.'org.opencontainers.image.version' }
164+
if (-not $RemoteBuildDate) { $RemoteBuildDate = $config.config.Labels.'org.opencontainers.image.created' }
160165
} catch {
161166
Write-Information "Could not read image config labels: $($_.Exception.Message)"
162167
}
@@ -179,6 +184,8 @@ function Start-ContainerUpdateCheck {
179184
RunningVersion = [string]($RunningVersion ?? '')
180185
RemoteVersion = [string]($RemoteVersion ?? '')
181186
RemoteDigest = [string]($RemoteDigest ?? '')
187+
RemoteBuildDate = [string]($RemoteBuildDate ?? '')
188+
CheckedTag = [string]($CheckTag ?? '')
182189
}
183190
Add-CIPPAzDataTableEntity @SettingsTable -Entity $UpdateEntity -Force | Out-Null
184191

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
function Sync-CippContainerUpdateState {
2+
<#
3+
.SYNOPSIS
4+
Reconcile the stored container update-check result with the build that is actually running.
5+
6+
.DESCRIPTION
7+
The ContainerUpdateSettings table is only written when an update check runs, so after a
8+
restart that applied an update the table keeps reporting the previous build's state —
9+
"update available" with the old running version — until the next check. This compares the
10+
stored result against the running APP_VERSION/IMAGE_TAG and clears or recomputes the flag
11+
locally, without a registry call. Returns the current settings entity, or $null when no
12+
settings have been saved yet.
13+
#>
14+
[CmdletBinding()]
15+
param()
16+
17+
$SettingsTable = Get-CippTable -tablename 'ContainerUpdateSettings'
18+
$Settings = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1
19+
if (-not $Settings) { return $null }
20+
21+
$RunningVersion = $env:APP_VERSION
22+
$StoredRunning = [string]($Settings.RunningVersion ?? '')
23+
if (-not $RunningVersion -or -not $StoredRunning -or $StoredRunning -eq $RunningVersion) {
24+
return $Settings
25+
}
26+
27+
# The container restarted onto a different build since the last check.
28+
$StoredRemote = [string]($Settings.RemoteVersion ?? '')
29+
$CheckedTag = [string]($Settings.CheckedTag ?? '')
30+
$RunningTag = $env:IMAGE_TAG
31+
if ($CheckedTag -and $RunningTag -and $CheckedTag -ne $RunningTag) {
32+
# The last check ran against a different channel tag — its result no longer applies.
33+
$UpdateAvailable = $false
34+
} else {
35+
$UpdateAvailable = [bool]($StoredRemote -and $StoredRemote -ne $RunningVersion)
36+
}
37+
38+
$Entity = @{
39+
PartitionKey = 'Settings'
40+
RowKey = 'UpdateConfig'
41+
AutoUpdate = [string]($Settings.AutoUpdate ?? 'false')
42+
CheckInterval = [string]($Settings.CheckInterval ?? '0')
43+
CheckTime = [string]($Settings.CheckTime ?? '')
44+
LastCheck = [string]($Settings.LastCheck ?? '')
45+
UpdateAvailable = [string]$UpdateAvailable
46+
RunningVersion = [string]$RunningVersion
47+
RemoteVersion = $StoredRemote
48+
RemoteDigest = [string]($Settings.RemoteDigest ?? '')
49+
RemoteBuildDate = [string]($Settings.RemoteBuildDate ?? '')
50+
CheckedTag = $CheckedTag
51+
}
52+
Add-CIPPAzDataTableEntity @SettingsTable -Entity $Entity -Force | Out-Null
53+
Write-Information "Container update state reconciled: running build changed '$StoredRunning' -> '$RunningVersion', UpdateAvailable=$UpdateAvailable"
54+
return [pscustomobject]$Entity
55+
}

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,12 @@ function Invoke-ExecContainerManagement {
6868
}
6969

7070
$version = $manifest.annotations.'org.opencontainers.image.version'
71-
if (-not $version -and $manifest.config.digest) {
71+
$created = $manifest.annotations.'org.opencontainers.image.created'
72+
if ((-not $version -or -not $created) -and $manifest.config.digest) {
7273
try {
7374
$config = Invoke-RestMethod -Uri "https://ghcr.io/v2/$imagePath/blobs/$($manifest.config.digest)" -Method GET -Headers $authHeader -ErrorAction Stop
74-
$version = $config.config.Labels.'org.opencontainers.image.version'
75+
if (-not $version) { $version = $config.config.Labels.'org.opencontainers.image.version' }
76+
if (-not $created) { $created = $config.config.Labels.'org.opencontainers.image.created' }
7577
} catch {
7678
Write-Information "Could not read image config labels for $($imagePath):$Tag$($_.Exception.Message)"
7779
}
@@ -80,6 +82,7 @@ function Invoke-ExecContainerManagement {
8082
return [pscustomobject]@{
8183
Digest = [string]$digest
8284
Version = [string]$version
85+
Created = [string]$created
8386
}
8487
}
8588

@@ -112,8 +115,10 @@ function Invoke-ExecContainerManagement {
112115
}
113116
}
114117

115-
# Read update settings and last check result
116-
$Settings = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1
118+
# Read update settings and last check result, reconciled against the running
119+
# build — a restart may have applied the previously detected update, which
120+
# would otherwise keep showing "update available" until the next check.
121+
$Settings = Sync-CippContainerUpdateState
117122
$UpdateInfo = @{
118123
AutoUpdate = $false
119124
CheckInterval = '0'
@@ -123,6 +128,7 @@ function Invoke-ExecContainerManagement {
123128
RunningVersion = $null
124129
RemoteVersion = $null
125130
RemoteDigest = $null
131+
RemoteBuildDate = $null
126132
}
127133
if ($Settings) {
128134
$UpdateInfo.AutoUpdate = $Settings.AutoUpdate -eq 'true'
@@ -133,13 +139,15 @@ function Invoke-ExecContainerManagement {
133139
$UpdateInfo.RunningVersion = $Settings.RunningVersion ?? $null
134140
$UpdateInfo.RemoteVersion = $Settings.RemoteVersion ?? $null
135141
$UpdateInfo.RemoteDigest = $Settings.RemoteDigest ?? $null
142+
$UpdateInfo.RemoteBuildDate = $Settings.RemoteBuildDate ?? $null
136143
}
137144

138145
$Body = @{
139146
Results = @{
140147
CurrentVersion = $CurrentVersion
141148
CommitSha = $CommitSha
142149
ImageTag = $ImageTag
150+
BuildDate = $env:BUILD_DATE ?? 'unknown'
143151
CurrentChannel = $CurrentChannel
144152
ConfiguredChannel = $ConfiguredChannel
145153
CurrentImage = $CurrentImage
@@ -199,6 +207,7 @@ function Invoke-ExecContainerManagement {
199207
$RemoteInfo = Get-GHCRImageInfo -ImageRef $CurrentImage -Tag $CheckTag
200208
$RemoteVersion = $RemoteInfo.Version
201209
$RemoteDigest = $RemoteInfo.Digest
210+
$RemoteBuildDate = $RemoteInfo.Created
202211

203212
$RunningVersion = $env:APP_VERSION
204213
$UpdateAvailable = $false
@@ -214,6 +223,8 @@ function Invoke-ExecContainerManagement {
214223
RunningVersion = [string]($RunningVersion ?? '')
215224
RemoteVersion = [string]($RemoteVersion ?? '')
216225
RemoteDigest = [string]($RemoteDigest ?? '')
226+
RemoteBuildDate = [string]($RemoteBuildDate ?? '')
227+
CheckedTag = [string]($CheckTag ?? '')
217228
}
218229
$Existing = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1
219230
if ($Existing) {
@@ -241,6 +252,7 @@ function Invoke-ExecContainerManagement {
241252
RunningVersion = $RunningVersion
242253
RemoteVersion = $RemoteVersion
243254
RemoteDigest = $RemoteDigest
255+
RemoteBuildDate = $RemoteBuildDate
244256
CheckedTag = $CheckTag
245257
}
246258
}
@@ -266,7 +278,8 @@ function Invoke-ExecContainerManagement {
266278
throw "Invalid check time: $CheckTime. Must be 0-23 (UTC hour)."
267279
}
268280

269-
# Read existing settings to preserve check results
281+
# Read existing settings to preserve check results — the upsert replaces the
282+
# whole entity, so every check-result field must be carried over here.
270283
$Existing = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1
271284
$Entity = @{
272285
PartitionKey = 'Settings'
@@ -276,8 +289,11 @@ function Invoke-ExecContainerManagement {
276289
CheckTime = [string]($CheckTime ?? '')
277290
LastCheck = [string]($Existing.LastCheck ?? '')
278291
UpdateAvailable = [string]($Existing.UpdateAvailable ?? 'false')
279-
RunningDigest = [string]($Existing.RunningDigest ?? '')
292+
RunningVersion = [string]($Existing.RunningVersion ?? '')
293+
RemoteVersion = [string]($Existing.RemoteVersion ?? '')
280294
RemoteDigest = [string]($Existing.RemoteDigest ?? '')
295+
RemoteBuildDate = [string]($Existing.RemoteBuildDate ?? '')
296+
CheckedTag = [string]($Existing.CheckedTag ?? '')
281297
}
282298
Add-CIPPAzDataTableEntity @SettingsTable -Entity $Entity -Force | Out-Null
283299

0 commit comments

Comments
 (0)