Skip to content

Commit

Permalink
Publish events when ClusterCreated or ClusterRemoved (#19)
Browse files Browse the repository at this point in the history
* Publish events when ClusterCreated or ClusterRemoved

* Add review changes

* Fix conflicts

* Fix syntax error

* Fix failing test
  • Loading branch information
sarataha authored Nov 10, 2023
1 parent 27e0965 commit 22d4822
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 3 deletions.
7 changes: 7 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ rules:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
Expand Down
21 changes: 18 additions & 3 deletions internal/controller/automatedclusterdiscovery_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,31 @@ import (

const k8sManagedByLabel = "app.kubernetes.io/managed-by"

type eventRecorder interface {
Event(object runtime.Object, eventtype, reason, message string)
}

// AutomatedClusterDiscoveryReconciler reconciles a AutomatedClusterDiscovery object
type AutomatedClusterDiscoveryReconciler struct {
client.Client
Scheme *runtime.Scheme
Scheme *runtime.Scheme
EventRecorder eventRecorder

AKSProvider func(string) providers.Provider
}

// event emits a Kubernetes event and forwards the event to the event recorder
func (r *AutomatedClusterDiscoveryReconciler) event(obj *clustersv1alpha1.AutomatedClusterDiscovery, eventtype, reason, message string) {
r.EventRecorder.Event(obj, eventtype, reason, message)
}

//+kubebuilder:rbac:groups=clusters.weave.works,resources=automatedclusterdiscoveries,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=clusters.weave.works,resources=automatedclusterdiscoveries/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=clusters.weave.works,resources=automatedclusterdiscoveries/finalizers,verbs=update
//+kubebuilder:rbac:groups=gitops.weave.works,resources=gitopsclusters,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down Expand Up @@ -238,7 +249,6 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont
}
gitopsCluster.SetLabels(labelsForResource(*acd))
gitopsCluster.SetAnnotations(acd.Spec.CommonAnnotations)

_, err = controllerutil.CreateOrPatch(ctx, r.Client, gitopsCluster, func() error {
gitopsCluster.Spec = gitopsv1alpha1.GitopsClusterSpec{
SecretRef: &meta.LocalObjectReference{
Expand Down Expand Up @@ -269,9 +279,11 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont
return inventoryResources, fmt.Errorf("failed to set ownership on created Secret: %w", err)
}

// publish event for ClusterCreated
r.event(acd, corev1.EventTypeNormal, "ClusterCreated", fmt.Sprintf("Cluster %s created", cluster.Name))

secret.SetLabels(labelsForResource(*acd))
secret.SetAnnotations(acd.Spec.CommonAnnotations)

_, err = controllerutil.CreateOrPatch(ctx, r.Client, secret, func() error {
value, err := clientcmd.Write(*cluster.KubeConfig)
if err != nil {
Expand Down Expand Up @@ -309,6 +321,9 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont
if err := r.Client.Delete(ctx, cluster); err != nil {
return inventoryResources, fmt.Errorf("failed to delete cluster: %w", err)
}

// publish event for ClusterRemoved
r.event(acd, corev1.EventTypeNormal, "ClusterRemoved", fmt.Sprintf("Cluster %s removed", cluster.GetName()))
}
}

Expand Down
103 changes: 103 additions & 0 deletions internal/controller/automatedclusterdiscovery_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: &mockEventRecorder{},
}

assert.NoError(t, reconciler.SetupWithManager(mgr))
Expand Down Expand Up @@ -200,6 +201,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: &mockEventRecorder{},
}

assert.NoError(t, reconciler.SetupWithManager(mgr))
Expand Down Expand Up @@ -279,6 +281,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: &mockEventRecorder{},
}

assert.NoError(t, reconciler.SetupWithManager(mgr))
Expand Down Expand Up @@ -350,6 +353,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: &mockEventRecorder{},
}
assert.NoError(t, reconciler.SetupWithManager(mgr))

Expand Down Expand Up @@ -422,6 +426,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: &mockEventRecorder{},
}
assert.NoError(t, reconciler.SetupWithManager(mgr))

Expand Down Expand Up @@ -488,6 +493,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: &mockEventRecorder{},
}

assert.NoError(t, reconciler.SetupWithManager(mgr))
Expand Down Expand Up @@ -552,6 +558,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: &mockEventRecorder{},
}
assert.NoError(t, reconciler.SetupWithManager(mgr))

Expand Down Expand Up @@ -642,6 +649,87 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
assert.Equal(t, "testing", aksCluster.Annotations[meta.ReconcileRequestAnnotation])
assert.Equal(t, "testing", aksCluster.Status.LastHandledReconcileAt)
})

t.Run("Reconcile publishes events on cluster creation and removal", func(t *testing.T) {
ctx := context.TODO()
aksCluster := &clustersv1alpha1.AutomatedClusterDiscovery{
ObjectMeta: metav1.ObjectMeta{
Name: "test-aks",
Namespace: "default",
},
Spec: clustersv1alpha1.AutomatedClusterDiscoverySpec{
Type: "aks",
AKS: &clustersv1alpha1.AKS{
SubscriptionID: "subscription-123",
},
Interval: metav1.Duration{Duration: time.Minute},
},
}

err := k8sClient.Create(ctx, aksCluster)
assert.NoError(t, err)
defer deleteClusterDiscoveryAndInventory(t, k8sClient, aksCluster)

cluster := &providers.ProviderCluster{
Name: "cluster-1",
KubeConfig: &kubeconfig.Config{
APIVersion: "v1",
Clusters: map[string]*kubeconfig.Cluster{
"cluster-1": {
Server: "https://cluster-prod.example.com/",
CertificateAuthorityData: []uint8(testCAData),
},
},
},
}

testProvider := stubProvider{
response: []*providers.ProviderCluster{
cluster,
},
}

mockEventRecorder := &mockEventRecorder{}

reconciler := &AutomatedClusterDiscoveryReconciler{
Client: k8sClient,
Scheme: scheme,
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
EventRecorder: mockEventRecorder,
}
assert.NoError(t, reconciler.SetupWithManager(mgr))

_, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(aksCluster)})
assert.NoError(t, err)

err = k8sClient.Get(ctx, client.ObjectKeyFromObject(aksCluster), aksCluster)
assert.NoError(t, err)

secret := newSecret(types.NamespacedName{Name: "cluster-1-kubeconfig", Namespace: aksCluster.GetNamespace()})
gitopsCluster := newGitopsCluster(secret.GetName(), types.NamespacedName{Name: "cluster-1", Namespace: aksCluster.GetNamespace()})
assertInventoryHasItems(t, aksCluster, secret, gitopsCluster)

assert.Equal(t, "Normal", mockEventRecorder.CapturedType)
assert.Equal(t, "ClusterCreated", mockEventRecorder.CapturedReason)
assert.Equal(t, "Cluster cluster-1 created", mockEventRecorder.CapturedMessage)

testProvider.response = []*providers.ProviderCluster{}

_, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(aksCluster)})
assert.NoError(t, err)

err = k8sClient.Get(ctx, client.ObjectKeyFromObject(aksCluster), aksCluster)
assert.NoError(t, err)

assertInventoryHasNoItems(t, aksCluster)

assert.Equal(t, "Normal", mockEventRecorder.CapturedType)
assert.Equal(t, "ClusterRemoved", mockEventRecorder.CapturedReason)
assert.Equal(t, "Cluster cluster-1 removed", mockEventRecorder.CapturedMessage)
})

}

func TestReconcilingWithAnnotationChange(t *testing.T) {
Expand Down Expand Up @@ -698,6 +786,7 @@ func TestReconcilingWithAnnotationChange(t *testing.T) {
AKSProvider: func(providerID string) providers.Provider {
return &stubProvider{}
},
EventRecorder: &mockEventRecorder{},
}
assert.NoError(t, reconciler.SetupWithManager(mgr))

Expand Down Expand Up @@ -726,6 +815,20 @@ func TestReconcilingWithAnnotationChange(t *testing.T) {
assert.Equal(t, aksCluster.Status.LastHandledReconcileAt, "testing")
}

type mockEventRecorder struct {
CapturedObj runtime.Object
CapturedType string
CapturedReason string
CapturedMessage string
}

func (m *mockEventRecorder) Event(object runtime.Object, eventtype, reason, message string) {
m.CapturedObj = object
m.CapturedType = eventtype
m.CapturedReason = reason
m.CapturedMessage = message
}

type stubProvider struct {
response []*providers.ProviderCluster
clusterID string
Expand Down

0 comments on commit 22d4822

Please sign in to comment.