Skip to content

Commit 99cef50

Browse files
committed
Add Application's health check
1 parent d262906 commit 99cef50

File tree

3 files changed

+125
-98
lines changed

3 files changed

+125
-98
lines changed

internal/controller/argo.go

Lines changed: 109 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ g, admin, role:admin`
9191
MountPath: "/etc/pki/tls/certs",
9292
},
9393
}
94-
9594
initContainers := []v1.Container{
9695
{
9796
Name: "fetch-ca",
@@ -117,6 +116,114 @@ g, admin, role:admin`
117116
},
118117
},
119118
}
119+
resourceHealthChecks := []argooperator.ResourceHealthCheck{
120+
{
121+
// We can drop this custom Subscription healthcheck once https://www.github.com/argoproj/argo-cd/issues/25921 is fixed
122+
Group: "operators.coreos.com",
123+
Kind: "Subscription",
124+
Check: `local health_status = {}
125+
if obj.status ~= nil then
126+
if obj.status.conditions ~= nil then
127+
local numDegraded = 0
128+
local numPending = 0
129+
local msg = ""
130+
131+
-- Check if this is a manual approval scenario where InstallPlanPending is expected
132+
-- and the operator is already installed (upgrade pending, not initial install)
133+
local isManualApprovalPending = false
134+
if obj.spec ~= nil and obj.spec.installPlanApproval == "Manual" then
135+
for _, condition in pairs(obj.status.conditions) do
136+
if condition.type == "InstallPlanPending" and condition.status == "True" and condition.reason == "RequiresApproval" then
137+
-- Only treat as expected healthy state if the operator is already installed
138+
-- (installedCSV is present), meaning this is an upgrade pending approval
139+
if obj.status.installedCSV ~= nil then
140+
isManualApprovalPending = true
141+
end
142+
break
143+
end
144+
end
145+
end
146+
147+
for i, condition in pairs(obj.status.conditions) do
148+
-- Skip InstallPlanPending condition when manual approval is pending (expected behavior)
149+
if isManualApprovalPending and condition.type == "InstallPlanPending" then
150+
-- Do not include in message or count as pending
151+
else
152+
msg = msg .. i .. ": " .. condition.type .. " | " .. condition.status .. "\n"
153+
if condition.type == "InstallPlanPending" and condition.status == "True" then
154+
numPending = numPending + 1
155+
elseif (condition.type == "InstallPlanMissing" and condition.reason ~= "ReferencedInstallPlanNotFound") then
156+
numDegraded = numDegraded + 1
157+
elseif (condition.type == "CatalogSourcesUnhealthy" or condition.type == "InstallPlanFailed" or condition.type == "ResolutionFailed") and condition.status == "True" then
158+
numDegraded = numDegraded + 1
159+
end
160+
end
161+
end
162+
163+
-- Available states: undef/nil, UpgradeAvailable, UpgradePending, UpgradeFailed, AtLatestKnown
164+
-- Source: https://github.com/openshift/operator-framework-olm/blob/5e2c73b7663d0122c9dc3e59ea39e515a31e2719/staging/api/pkg/operators/v1alpha1/subscription_types.go#L17-L23
165+
if obj.status.state == nil then
166+
numPending = numPending + 1
167+
msg = msg .. ".status.state not yet known\n"
168+
elseif obj.status.state == "" or obj.status.state == "UpgradeAvailable" then
169+
numPending = numPending + 1
170+
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
171+
elseif obj.status.state == "UpgradePending" then
172+
-- UpgradePending with manual approval is expected behavior, treat as healthy
173+
if isManualApprovalPending then
174+
msg = msg .. ".status.state is 'AtLatestKnown'\n"
175+
else
176+
numPending = numPending + 1
177+
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
178+
end
179+
elseif obj.status.state == "UpgradeFailed" then
180+
numDegraded = numDegraded + 1
181+
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
182+
else
183+
-- Last possiblity of .status.state: AtLatestKnown
184+
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
185+
end
186+
187+
if numDegraded == 0 and numPending == 0 then
188+
health_status.status = "Healthy"
189+
health_status.message = msg
190+
return health_status
191+
elseif numPending > 0 and numDegraded == 0 then
192+
health_status.status = "Progressing"
193+
health_status.message = msg
194+
return health_status
195+
else
196+
health_status.status = "Degraded"
197+
health_status.message = msg
198+
return health_status
199+
end
200+
end
201+
end
202+
health_status.status = "Progressing"
203+
health_status.message = "An install plan for a subscription is pending installation"
204+
return health_status`,
205+
},
206+
}
207+
if strings.EqualFold(PatternsOperatorConfig.getValueWithDefault("gitops.applicationHealthCheckEnabled"), "true") {
208+
// As of ArgoCD 1.8 the Application health check was dropped (see https://github.com/argoproj/argo-cd/issues/3781),
209+
// but in app-of-apps pattern this is needed in order to implement children apps dependencies via sync-waves
210+
resourceHealthChecks = append(resourceHealthChecks, argooperator.ResourceHealthCheck{
211+
Group: "argoproj.io",
212+
Kind: "Application",
213+
Check: `local health_status = {}
214+
health_status.status = "Progressing"
215+
health_status.message = ""
216+
if obj.status ~= nil then
217+
if obj.status.health ~= nil then
218+
health_status.status = obj.status.health.status
219+
if obj.status.health.message ~= nil then
220+
health_status.message = obj.status.health.message
221+
end
222+
end
223+
end
224+
return health_status`,
225+
})
226+
}
120227

121228
s := argooperator.ArgoCD{
122229
TypeMeta: metav1.TypeMeta{
@@ -247,94 +354,7 @@ g, admin, role:admin`
247354
kinds:
248355
- TaskRun
249356
- PipelineRun`,
250-
// We can drop this custom Subscription healthcheck once https://www.github.com/argoproj/argo-cd/issues/25921 is fixed
251-
ResourceHealthChecks: []argooperator.ResourceHealthCheck{
252-
{
253-
Group: "operators.coreos.com",
254-
Kind: "Subscription",
255-
Check: `local health_status = {}
256-
if obj.status ~= nil then
257-
if obj.status.conditions ~= nil then
258-
local numDegraded = 0
259-
local numPending = 0
260-
local msg = ""
261-
262-
-- Check if this is a manual approval scenario where InstallPlanPending is expected
263-
-- and the operator is already installed (upgrade pending, not initial install)
264-
local isManualApprovalPending = false
265-
if obj.spec ~= nil and obj.spec.installPlanApproval == "Manual" then
266-
for _, condition in pairs(obj.status.conditions) do
267-
if condition.type == "InstallPlanPending" and condition.status == "True" and condition.reason == "RequiresApproval" then
268-
-- Only treat as expected healthy state if the operator is already installed
269-
-- (installedCSV is present), meaning this is an upgrade pending approval
270-
if obj.status.installedCSV ~= nil then
271-
isManualApprovalPending = true
272-
end
273-
break
274-
end
275-
end
276-
end
277-
278-
for i, condition in pairs(obj.status.conditions) do
279-
-- Skip InstallPlanPending condition when manual approval is pending (expected behavior)
280-
if isManualApprovalPending and condition.type == "InstallPlanPending" then
281-
-- Do not include in message or count as pending
282-
else
283-
msg = msg .. i .. ": " .. condition.type .. " | " .. condition.status .. "\n"
284-
if condition.type == "InstallPlanPending" and condition.status == "True" then
285-
numPending = numPending + 1
286-
elseif (condition.type == "InstallPlanMissing" and condition.reason ~= "ReferencedInstallPlanNotFound") then
287-
numDegraded = numDegraded + 1
288-
elseif (condition.type == "CatalogSourcesUnhealthy" or condition.type == "InstallPlanFailed" or condition.type == "ResolutionFailed") and condition.status == "True" then
289-
numDegraded = numDegraded + 1
290-
end
291-
end
292-
end
293-
294-
-- Available states: undef/nil, UpgradeAvailable, UpgradePending, UpgradeFailed, AtLatestKnown
295-
-- Source: https://github.com/openshift/operator-framework-olm/blob/5e2c73b7663d0122c9dc3e59ea39e515a31e2719/staging/api/pkg/operators/v1alpha1/subscription_types.go#L17-L23
296-
if obj.status.state == nil then
297-
numPending = numPending + 1
298-
msg = msg .. ".status.state not yet known\n"
299-
elseif obj.status.state == "" or obj.status.state == "UpgradeAvailable" then
300-
numPending = numPending + 1
301-
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
302-
elseif obj.status.state == "UpgradePending" then
303-
-- UpgradePending with manual approval is expected behavior, treat as healthy
304-
if isManualApprovalPending then
305-
msg = msg .. ".status.state is 'AtLatestKnown'\n"
306-
else
307-
numPending = numPending + 1
308-
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
309-
end
310-
elseif obj.status.state == "UpgradeFailed" then
311-
numDegraded = numDegraded + 1
312-
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
313-
else
314-
-- Last possiblity of .status.state: AtLatestKnown
315-
msg = msg .. ".status.state is '" .. obj.status.state .. "'\n"
316-
end
317-
318-
if numDegraded == 0 and numPending == 0 then
319-
health_status.status = "Healthy"
320-
health_status.message = msg
321-
return health_status
322-
elseif numPending > 0 and numDegraded == 0 then
323-
health_status.status = "Progressing"
324-
health_status.message = msg
325-
return health_status
326-
else
327-
health_status.status = "Degraded"
328-
health_status.message = msg
329-
return health_status
330-
end
331-
end
332-
end
333-
health_status.status = "Progressing"
334-
health_status.message = "An install plan for a subscription is pending installation"
335-
return health_status`,
336-
},
337-
},
357+
ResourceHealthChecks: resourceHealthChecks,
338358
ResourceTrackingMethod: "annotation",
339359
Server: argooperator.ArgoCDServerSpec{
340360
Autoscale: argooperator.ArgoCDServerAutoscaleSpec{

internal/controller/defaults.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,16 @@ const (
8383
// Currently none
8484

8585
var DefaultPatternOperatorConfig = map[string]string{
86-
"gitops.catalogSource": GitOpsDefaultCatalogSource,
87-
"gitops.channel": GitOpsDefaultChannel,
88-
"gitops.sourceNamespace": GitOpsDefaultCatalogSourceNamespace,
89-
"gitops.installApprovalPlan": GitOpsDefaultApprovalPlan,
90-
"gitops.csv": GitOpsDefaultCSV,
91-
"gitea.chartName": GiteaChartName,
92-
"gitea.helmRepoUrl": GiteaHelmRepoUrl,
93-
"gitea.chartVersion": GiteaDefaultChartVersion,
94-
"analytics.enabled": "true",
86+
"gitops.catalogSource": GitOpsDefaultCatalogSource,
87+
"gitops.channel": GitOpsDefaultChannel,
88+
"gitops.sourceNamespace": GitOpsDefaultCatalogSourceNamespace,
89+
"gitops.installApprovalPlan": GitOpsDefaultApprovalPlan,
90+
"gitops.csv": GitOpsDefaultCSV,
91+
"gitops.applicationHealthCheckEnabled": "false",
92+
"gitea.chartName": GiteaChartName,
93+
"gitea.helmRepoUrl": GiteaHelmRepoUrl,
94+
"gitea.chartVersion": GiteaDefaultChartVersion,
95+
"analytics.enabled": "true",
9596
}
9697

9798
type GitOpsConfig map[string]string

internal/controller/defaults_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ var _ = Describe("GitOpsConfig getValueWithDefault", func() {
3636
Expect(config.getValueWithDefault("gitops.installApprovalPlan")).To(Equal(GitOpsDefaultApprovalPlan))
3737
})
3838

39+
It("should return the default value for gitops.applicationHealthCheckEnabled", func() {
40+
config := GitOpsConfig{}
41+
Expect(config.getValueWithDefault("gitops.applicationHealthCheckEnabled")).To(Equal("true"))
42+
})
43+
3944
It("should return the default value for gitea.chartName", func() {
4045
config := GitOpsConfig{}
4146
Expect(config.getValueWithDefault("gitea.chartName")).To(Equal(GiteaChartName))
@@ -88,6 +93,7 @@ var _ = Describe("DefaultPatternOperatorConfig", func() {
8893
"gitops.channel",
8994
"gitops.sourceNamespace",
9095
"gitops.installApprovalPlan",
96+
"gitops.applicationHealthCheckEnabled",
9197
"gitea.chartName",
9298
"gitea.helmRepoUrl",
9399
"gitea.chartVersion",

0 commit comments

Comments
 (0)