feat: add health check for MedusaTask (issue #28007)
Project: argoproj/argo-cd | Language: Go (with Lua health check) | Concepts: testing-strategy, error-handling, observability | Difficulty: mid
Link: https://github.com/argoproj/argo-cd/pull/28006
The problem
Argo CD did not have a health check for the MedusaTask custom resource. This meant the system could not automatically determine or display the health status of MedusaTask objects, making it harder to observe, debug, or automate workflows based on their state.
Why it's non-trivial
A naive solution might only check for the presence of a status field or a simple "success/failure" flag. However, MedusaTask uses several fields (finished, failed, and observedGeneration) to indicate state, and these must be interpreted together to accurately determine health. Additionally, the health check must distinguish between "not started", "in progress", "healthy", and "degraded" states, and handle resource versioning via generation and observedGeneration.
The approach
The PR introduces a Lua script for health checking and a suite of test cases. Here are the key logic snippets and what they do:
if obj.status == nil then
hs.message = "No status object"
return hs
end
Checks if the status field is missing. If so, the health is unknown and a message is returned. This prevents null reference errors and gives a clear reason for the unknown state.
if obj.status.observedGeneration == nil then
hs.message = "No observedGeneration"
return hs
end
Checks if observedGeneration is missing. This field is necessary to know if the controller has processed the current spec. If missing, health is unknown.
if obj.status.observedGeneration ~= obj.metadata.generation then
hs.message = "observedGeneration is not equal to generation"
hs.status = "Progressing"
return hs
end
If the controller has not yet observed the latest spec (generation), the resource is considered "Progressing". This avoids marking a resource as healthy/degraded before the controller has caught up.
if obj.status.finished == nil and obj.status.failed == nil then
hs.status = "Progressing"
return hs
end
If neither finished nor failed is set, the task is still in progress.
if obj.status.finished ~= nil then
if obj.status.finished[0] or obj.status.finished[1] then
hs.status = "Healthy"
else
hs.status = "Progressing"
end
end
If there are entries in the finished array, the task is considered healthy. If the array is empty, it's still progressing.
if obj.status.failed ~= nil then
hs.status = "Degraded"
hs.message = "Failed nodes exist"
end
If there are any entries in the failed array, the task is marked as degraded, and a message is set.
The PR also adds YAML test cases covering all these scenarios, ensuring the logic is exercised and correct.
From the review thread
crenshaw-dev: I think in the case
observerGenerationis nil, it makes sense to returnUnknown. But if it's present and just doesn't matchgeneration, I think it would make more sense to returnProgressing.
Takeaways
- When designing health checks for custom resources, use all relevant status fields and versioning metadata to accurately reflect the resource's state.
- Always handle missing or incomplete status fields gracefully, returning "Unknown" or similar, to avoid misleading health signals or runtime errors.
- Back up new health check logic with comprehensive, scenario-based tests to ensure correct behavior across all edge cases.