Files
cd/test/e2e/clusterdecisiongenerator_e2e_test.go
zeekayandHanzo Dev cbf668a980 Build on Go 1.27
go fix applied the modernizers: promoted-field keys in struct literals,
strings.Cut, SplitSeq, new(expr), slices and maps helpers, wg.Go, reflect.TypeFor
and errors.AsType. omitzero is left out, since omitting a zero struct such as
time.Time{} is a per-field wire decision and not a mechanical one.

Two changes the modernizers could not make for themselves. AsType needs its type
argument to be an error, so the git exit-status reader embeds error in the
interface it asks for. And an error built by concatenating a tag list into its
own format string now says so with a verb, which is what vet was pointing at.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 01:47:46 -07:00

488 lines
17 KiB
Go

package e2e
import (
"testing"
"github.com/hanzoai/cd/test/e2e/fixture"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/hanzoai/cd/pkg/apis/application/v1alpha1"
. "github.com/hanzoai/cd/test/e2e/fixture/applicationsets"
"github.com/hanzoai/cd/test/e2e/fixture/applicationsets/utils"
"github.com/hanzoai/cd/pkg/apis/application"
)
func TestSimpleClusterDecisionResourceGeneratorExternalNamespace(t *testing.T) {
externalNamespace := string(utils.CDExternalNamespace)
expectedApp := v1alpha1.Application{
Kind: "Application",
APIVersion: "apps.hanzo.ai/v1alpha1",
Name: "cluster1-guestbook",
Namespace: externalNamespace,
Finalizers: []string{v1alpha1.ResourcesFinalizerName},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "cluster1",
Namespace: "guestbook",
},
},
}
var expectedAppNewNamespace *v1alpha1.Application
var expectedAppNewMetadata *v1alpha1.Application
clusterList := []any{
map[string]any{
"clusterName": "cluster1",
"reason": "test-reason",
},
}
Given(t).
// Create a ClusterGenerator-based ApplicationSet
When().
CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc").
CreatePlacementRoleAndRoleBinding().
CreatePlacementDecisionConfigMap("my-configmap").
CreatePlacementDecision("my-placementdecision").
StatusUpdatePlacementDecision("my-placementdecision", clusterList).
SwitchToExternalNamespace(utils.CDExternalNamespace).
Create(v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{clusterName}}",
// Server: "{{server}}",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
ClusterDecisionResource: &v1alpha1.DuckTypeGenerator{
ConfigMapRef: "my-configmap",
Name: "my-placementdecision",
},
},
},
},
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedApp})).
// Update the ApplicationSet template namespace, and verify it updates the Applications
When().
And(func() {
expectedAppNewNamespace = expectedApp.DeepCopy()
expectedAppNewNamespace.Spec.Destination.Namespace = "guestbook2"
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Spec.Destination.Namespace = "guestbook2"
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{*expectedAppNewNamespace})).
// Update the metadata fields in the appset template, and make sure it propagates to the apps
When().
And(func() {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
appset.Spec.Template.Labels = map[string]string{
"label-key": "label-value",
}
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{*expectedAppNewMetadata})).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete(metav1.DeletePropagationForeground).Then().Expect(ApplicationsDoNotExist([]v1alpha1.Application{*expectedAppNewNamespace}))
}
func TestSimpleClusterDecisionResourceGenerator(t *testing.T) {
expectedApp := v1alpha1.Application{
Kind: application.ApplicationKind,
APIVersion: "apps.hanzo.ai/v1alpha1",
Name: "cluster1-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{v1alpha1.ResourcesFinalizerName},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "cluster1",
Namespace: "guestbook",
},
},
}
var expectedAppNewNamespace *v1alpha1.Application
var expectedAppNewMetadata *v1alpha1.Application
clusterList := []any{
map[string]any{
"clusterName": "cluster1",
"reason": "test-reason",
},
}
Given(t).
// Create a ClusterGenerator-based ApplicationSet
When().
CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc").
CreatePlacementRoleAndRoleBinding().
CreatePlacementDecisionConfigMap("my-configmap").
CreatePlacementDecision("my-placementdecision").
StatusUpdatePlacementDecision("my-placementdecision", clusterList).
Create(v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{clusterName}}",
// Server: "{{server}}",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
ClusterDecisionResource: &v1alpha1.DuckTypeGenerator{
ConfigMapRef: "my-configmap",
Name: "my-placementdecision",
},
},
},
},
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedApp})).
// Update the ApplicationSet template namespace, and verify it updates the Applications
When().
And(func() {
expectedAppNewNamespace = expectedApp.DeepCopy()
expectedAppNewNamespace.Spec.Destination.Namespace = "guestbook2"
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Spec.Destination.Namespace = "guestbook2"
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{*expectedAppNewNamespace})).
// Update the metadata fields in the appset template, and make sure it propagates to the apps
When().
And(func() {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
appset.Spec.Template.Labels = map[string]string{"label-key": "label-value"}
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{*expectedAppNewMetadata})).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete(metav1.DeletePropagationForeground).Then().Expect(ApplicationsDoNotExist([]v1alpha1.Application{*expectedAppNewNamespace}))
}
func TestSimpleClusterDecisionResourceGeneratorAddingCluster(t *testing.T) {
expectedAppTemplate := v1alpha1.Application{
Kind: application.ApplicationKind,
APIVersion: "apps.hanzo.ai/v1alpha1",
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{v1alpha1.ResourcesFinalizerName},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{clusterName}}",
Namespace: "guestbook",
},
},
}
expectedAppCluster1 := *expectedAppTemplate.DeepCopy()
expectedAppCluster1.Spec.Destination.Name = "cluster1"
expectedAppCluster1.Name = "cluster1-guestbook"
expectedAppCluster2 := *expectedAppTemplate.DeepCopy()
expectedAppCluster2.Spec.Destination.Name = "cluster2"
expectedAppCluster2.Name = "cluster2-guestbook"
clusterList := []any{
map[string]any{
"clusterName": "cluster1",
"reason": "test-reason",
},
map[string]any{
"clusterName": "cluster2",
"reason": "test-reason",
},
}
Given(t).
// Create a ClusterGenerator-based ApplicationSet
When().
CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc").
CreatePlacementRoleAndRoleBinding().
CreatePlacementDecisionConfigMap("my-configmap").
CreatePlacementDecision("my-placementdecision").
StatusUpdatePlacementDecision("my-placementdecision", clusterList).
Create(v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{clusterName}}",
// Server: "{{server}}",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
ClusterDecisionResource: &v1alpha1.DuckTypeGenerator{
ConfigMapRef: "my-configmap",
Name: "my-placementdecision",
RequeueAfterSeconds: new(int64(10)),
},
},
},
},
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedAppCluster1})).
// Update the ApplicationSet template namespace, and verify it updates the Applications
When().
CreateClusterSecret("my-secret2", "cluster2", "https://kubernetes.default.svc").
Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedAppCluster1, expectedAppCluster2})).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete(metav1.DeletePropagationForeground).Then().Expect(ApplicationsDoNotExist([]v1alpha1.Application{expectedAppCluster1, expectedAppCluster2}))
}
func TestSimpleClusterDecisionResourceGeneratorDeletingClusterSecret(t *testing.T) {
expectedAppTemplate := v1alpha1.Application{
Kind: application.ApplicationKind,
APIVersion: "apps.hanzo.ai/v1alpha1",
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{v1alpha1.ResourcesFinalizerName},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{name}}",
Namespace: "guestbook",
},
},
}
expectedAppCluster1 := *expectedAppTemplate.DeepCopy()
expectedAppCluster1.Spec.Destination.Name = "cluster1"
expectedAppCluster1.Name = "cluster1-guestbook"
expectedAppCluster2 := *expectedAppTemplate.DeepCopy()
expectedAppCluster2.Spec.Destination.Name = "cluster2"
expectedAppCluster2.Name = "cluster2-guestbook"
clusterList := []any{
map[string]any{
"clusterName": "cluster1",
"reason": "test-reason",
},
map[string]any{
"clusterName": "cluster2",
"reason": "test-reason",
},
}
Given(t).
// Create a ClusterGenerator-based ApplicationSet
When().
CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc").
CreateClusterSecret("my-secret2", "cluster2", "https://kubernetes.default.svc").
CreatePlacementRoleAndRoleBinding().
CreatePlacementDecisionConfigMap("my-configmap").
CreatePlacementDecision("my-placementdecision").
StatusUpdatePlacementDecision("my-placementdecision", clusterList).
Create(v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{clusterName}}",
// Server: "{{server}}",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
ClusterDecisionResource: &v1alpha1.DuckTypeGenerator{
ConfigMapRef: "my-configmap",
Name: "my-placementdecision",
RequeueAfterSeconds: new(int64(10)),
},
},
},
},
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedAppCluster1, expectedAppCluster2})).
// Update the ApplicationSet template namespace, and verify it updates the Applications
When().
DeleteClusterSecret("my-secret2").
Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedAppCluster1})).
Expect(ApplicationsDoNotExist([]v1alpha1.Application{expectedAppCluster2})).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete(metav1.DeletePropagationForeground).Then().Expect(ApplicationsDoNotExist([]v1alpha1.Application{expectedAppCluster1}))
}
func TestSimpleClusterDecisionResourceGeneratorDeletingClusterFromResource(t *testing.T) {
expectedAppTemplate := v1alpha1.Application{
Kind: application.ApplicationKind,
APIVersion: "apps.hanzo.ai/v1alpha1",
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{v1alpha1.ResourcesFinalizerName},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{name}}",
Namespace: "guestbook",
},
},
}
expectedAppCluster1 := *expectedAppTemplate.DeepCopy()
expectedAppCluster1.Spec.Destination.Name = "cluster1"
expectedAppCluster1.Name = "cluster1-guestbook"
expectedAppCluster2 := *expectedAppTemplate.DeepCopy()
expectedAppCluster2.Spec.Destination.Name = "cluster2"
expectedAppCluster2.Name = "cluster2-guestbook"
clusterList := []any{
map[string]any{
"clusterName": "cluster1",
"reason": "test-reason",
},
map[string]any{
"clusterName": "cluster2",
"reason": "test-reason",
},
}
clusterListSmall := []any{
map[string]any{
"clusterName": "cluster1",
"reason": "test-reason",
},
}
Given(t).
// Create a ClusterGenerator-based ApplicationSet
When().
CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc").
CreateClusterSecret("my-secret2", "cluster2", "https://kubernetes.default.svc").
CreatePlacementRoleAndRoleBinding().
CreatePlacementDecisionConfigMap("my-configmap").
CreatePlacementDecision("my-placementdecision").
StatusUpdatePlacementDecision("my-placementdecision", clusterList).
Create(v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/hanzocd/example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: v1alpha1.ApplicationDestination{
Name: "{{clusterName}}",
// Server: "{{server}}",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
ClusterDecisionResource: &v1alpha1.DuckTypeGenerator{
ConfigMapRef: "my-configmap",
Name: "my-placementdecision",
RequeueAfterSeconds: new(int64(10)),
},
},
},
},
}).Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedAppCluster1, expectedAppCluster2})).
// Update the ApplicationSet template namespace, and verify it updates the Applications
When().
StatusUpdatePlacementDecision("my-placementdecision", clusterListSmall).
Then().Expect(ApplicationsExist([]v1alpha1.Application{expectedAppCluster1})).
Expect(ApplicationsDoNotExist([]v1alpha1.Application{expectedAppCluster2})).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete(metav1.DeletePropagationForeground).Then().Expect(ApplicationsDoNotExist([]v1alpha1.Application{expectedAppCluster1}))
}