feat(appset): add Health field to ApplicationSet status (#25753)

Signed-off-by: Peter Jiang <peterjiang823@gmail.com>
This commit is contained in:
Peter Jiang
2026-01-12 06:35:49 -08:00
committed by GitHub
parent 3c01ab15ee
commit 05504d623c
19 changed files with 1252 additions and 789 deletions

View File

@@ -2174,3 +2174,78 @@ func TestApplicationSetAPIListResourceEvents(t *testing.T) {
}).
When().Delete().Then().Expect(ApplicationsDoNotExist([]v1alpha1.Application{}))
}
// TestApplicationSetHealthStatusCLI tests that the CLI commands display the health status field for an ApplicationSet.
func TestApplicationSetHealthStatusCLI(t *testing.T) {
expectedApp := v1alpha1.Application{
TypeMeta: metav1.TypeMeta{
Kind: application.ApplicationKind,
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "health-cli-guestbook",
Namespace: utils.ArgoCDNamespace,
Finalizers: []string{v1alpha1.ResourcesFinalizerName},
},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
}
Given(t).
When().Create(v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "health-status-cli-test",
Namespace: utils.ArgoCDNamespace,
},
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "health-cli-guestbook"},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{
Raw: []byte(`{"cluster": "my-cluster"}`),
}},
},
},
},
},
}).Then().
// Wait for the ApplicationSet to be ready
Expect(ApplicationSetHasConditions("health-status-cli-test", ExpectedConditions)).
// Test 'argocd appset get' shows Health Status field
When().AppSetGet("health-status-cli-test").
Then().Expect(OutputContains("Health Status:")).
// Test 'argocd appset list' shows HEALTH column header
When().AppSetList().
Then().Expect(OutputContains("HEALTH")).
// Cleanup
When().Delete().Then().Expect(ApplicationsDoNotExist([]v1alpha1.Application{expectedApp}))
}

View File

@@ -537,6 +537,29 @@ func (a *Actions) AppSet(appName string, flags ...string) *Actions {
return a
}
// AppSetGet runs 'argocd appset get' CLI command and stores the output
func (a *Actions) AppSetGet(appSetName string, flags ...string) *Actions {
a.context.t.Helper()
args := []string{"appset", "get", appSetName}
args = append(args, flags...)
a.runCli(args...)
return a
}
// AppSetList runs 'argocd appset list' CLI command and stores the output
func (a *Actions) AppSetList(flags ...string) *Actions {
a.context.t.Helper()
args := []string{"appset", "list"}
args = append(args, flags...)
a.runCli(args...)
return a
}
// GetLastOutput returns the output from the last CLI command
func (a *Actions) GetLastOutput() string {
return a.lastOutput
}
func (a *Actions) runCli(args ...string) {
a.context.T().Helper()
a.lastOutput, a.lastError = fixture.RunCli(args...)

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/argoproj/gitops-engine/pkg/diff"
"github.com/argoproj/gitops-engine/pkg/health"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -39,6 +40,19 @@ func Success(message string) Expectation {
}
}
// OutputContains asserts that the last command output contains the expected substring
func OutputContains(expected string) Expectation {
return func(c *Consequences) (state, string) {
if c.actions.lastError != nil {
return failed, fmt.Sprintf("error: %v", c.actions.lastError)
}
if !strings.Contains(c.actions.lastOutput, expected) {
return failed, fmt.Sprintf("output did not contain '%s', got: %s", expected, c.actions.lastOutput)
}
return succeeded, fmt.Sprintf("output contained '%s'", expected)
}
}
// Error asserts that the last command was an error with substring match
func Error(message, err string) Expectation {
return func(c *Consequences) (state, string) {
@@ -79,6 +93,23 @@ func ApplicationsExist(expectedApps []v1alpha1.Application) Expectation {
}
}
// ApplicationSetHasHealthStatus checks whether the ApplicationSet has the expected health status.
func ApplicationSetHasHealthStatus(applicationSetName string, expectedHealthStatus health.HealthStatusCode) Expectation {
return func(c *Consequences) (state, string) {
// retrieve the application set
foundApplicationSet := c.applicationSet(applicationSetName)
if foundApplicationSet == nil {
return pending, fmt.Sprintf("application set '%s' not found", applicationSetName)
}
if foundApplicationSet.Status.Health.Status != expectedHealthStatus {
return pending, fmt.Sprintf("application set health status is '%s', expected '%s'",
foundApplicationSet.Status.Health.Status, expectedHealthStatus)
}
return succeeded, fmt.Sprintf("application set has expected health status '%s'", expectedHealthStatus)
}
}
// ApplicationSetHasConditions checks whether each of the 'expectedConditions' exist in the ApplicationSet status, and are
// equivalent to provided values.
func ApplicationSetHasConditions(expectedConditions []v1alpha1.ApplicationSetCondition) Expectation {