unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
testing-strategyerror-handlingobservability

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 observerGeneration is nil, it makes sense to return Unknown. But if it's present and just doesn't match generation, I think it would make more sense to return Progressing.

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.