-
Notifications
You must be signed in to change notification settings - Fork 23
/
clusterdeployment_controller.go
229 lines (191 loc) · 8.25 KB
/
clusterdeployment_controller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright (c) Red Hat, Inc.
// Copyright Contributors to the Open Cluster Management project
package clusterdeployment
import (
"context"
apiconstants "github.com/stolostron/cluster-lifecycle-api/constants"
"github.com/stolostron/managedcluster-import-controller/pkg/constants"
"github.com/stolostron/managedcluster-import-controller/pkg/helpers"
"github.com/stolostron/managedcluster-import-controller/pkg/source"
hivev1 "github.com/openshift/hive/apis/hive/v1"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resource/resourcemerge"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
clusterv1 "open-cluster-management.io/api/cluster/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
var log = logf.Log.WithName(controllerName)
// ReconcileClusterDeployment reconciles the clusterdeployment that is in the managed cluster namespace
// to import the managed cluster
type ReconcileClusterDeployment struct {
client client.Client
kubeClient kubernetes.Interface
informerHolder *source.InformerHolder
recorder events.Recorder
importHelper *helpers.ImportHelper
}
func NewReconcileClusterDeployment(
client client.Client,
kubeClient kubernetes.Interface,
informerHolder *source.InformerHolder,
recorder events.Recorder,
) *ReconcileClusterDeployment {
return &ReconcileClusterDeployment{
client: client,
kubeClient: kubeClient,
informerHolder: informerHolder,
recorder: recorder,
importHelper: helpers.NewImportHelper(informerHolder, recorder, log).
WithGenerateClientHolderFunc(helpers.GenerateImportClientFromKubeConfigSecret),
}
}
// blank assignment to verify that ReconcileClusterDeployment implements reconcile.Reconciler
var _ reconcile.Reconciler = &ReconcileClusterDeployment{}
// Reconcile the clusterdeployment that is in the managed cluster namespace to import the managed cluster.
//
// Note: The Controller will requeue the Request to be processed again if the returned error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
func (r *ReconcileClusterDeployment) Reconcile(
ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
reqLogger := log.WithValues("Request.Name", request.Name)
clusterName := request.Name
clusterDeployment := &hivev1.ClusterDeployment{}
err := r.client.Get(ctx, types.NamespacedName{Name: clusterName, Namespace: clusterName}, clusterDeployment)
if errors.IsNotFound(err) {
return reconcile.Result{}, nil
}
if err != nil {
return reconcile.Result{}, err
}
reqLogger.Info("Reconciling clusterdeployment")
if !clusterDeployment.DeletionTimestamp.IsZero() {
// We do not set this finalizer anymore, but we still need to remove it for backward compatible
// the clusterdeployment is deleting, its managed cluster may already be detached (the managed
// cluster has been deleted, but the namespace is remained), if it has import finalizer, we
// remove its namespace
return reconcile.Result{}, r.removeImportFinalizer(ctx, clusterDeployment)
}
managedCluster := &clusterv1.ManagedCluster{}
err = r.client.Get(ctx, types.NamespacedName{Name: clusterName}, managedCluster)
if errors.IsNotFound(err) {
// the managed cluster could be deleted, do nothing
return reconcile.Result{}, nil
}
if err != nil {
return reconcile.Result{}, err
}
if !managedCluster.DeletionTimestamp.IsZero() {
return reconcile.Result{}, nil
}
if _, autoImportDisabled := managedCluster.Annotations[apiconstants.DisableAutoImportAnnotation]; autoImportDisabled {
// skip if auto import is disabled
return reconcile.Result{}, nil
}
if !clusterDeployment.Spec.Installed {
// cluster deployment is not installed yet, do nothing
reqLogger.Info("The hive managed cluster is not installed, skipped", "managedcluster", clusterName)
return reconcile.Result{}, nil
}
if clusterDeployment.Spec.ClusterPoolRef != nil && clusterDeployment.Spec.ClusterPoolRef.ClaimedTimestamp.IsZero() {
// cluster deployment is not claimed yet, do nothing
reqLogger.Info("The hive managed cluster is not claimed, skipped", "managedcluster", clusterName)
return reconcile.Result{}, nil
}
// set managed cluster created-via annotation
if err := r.setCreatedViaAnnotation(ctx, clusterDeployment, managedCluster); err != nil {
return reconcile.Result{}, err
}
// if there is an auto import secret in the managed cluster namespace, we will use the auto import secret
// to import the cluster
_, err = r.informerHolder.AutoImportSecretLister.Secrets(clusterName).Get(constants.AutoImportSecretName)
if err == nil {
reqLogger.Info("The hive managed cluster has auto import secret, skipped", "managedcluster", clusterName)
return reconcile.Result{}, nil
}
if !errors.IsNotFound(err) {
return reconcile.Result{}, err
}
secretRefName := clusterDeployment.Spec.ClusterMetadata.AdminKubeconfigSecretRef.Name
hiveSecret, err := r.kubeClient.CoreV1().Secrets(clusterName).Get(ctx, secretRefName, metav1.GetOptions{})
if err != nil {
return reconcile.Result{}, err
}
result, condition, modified, _, iErr := r.importHelper.Import(false, clusterName, hiveSecret, 0, 1)
// if resources are applied but NOT modified, will not update the condition, keep the original condition.
// This check is to prevent the current controller and import status controller from modifying the
// ManagedClusterImportSucceeded condition of the managed cluster in a loop
if !helpers.ImportingResourcesApplied(&condition) || modified {
if err := helpers.UpdateManagedClusterStatus(
r.client,
clusterName,
condition,
); err != nil {
return reconcile.Result{}, err
}
}
return result, iErr
}
func (r *ReconcileClusterDeployment) setCreatedViaAnnotation(
ctx context.Context, clusterDeployment *hivev1.ClusterDeployment, cluster *clusterv1.ManagedCluster) error {
patch := client.MergeFrom(cluster.DeepCopy())
viaAnnotation := cluster.Annotations[constants.CreatedViaAnnotation]
if viaAnnotation == constants.CreatedViaDiscovery {
// create-via annotaion is discovery, do nothing
return nil
}
modified := resourcemerge.BoolPtr(false)
if clusterDeployment.Spec.Platform.AgentBareMetal != nil {
resourcemerge.MergeMap(modified,
&cluster.Annotations, map[string]string{constants.CreatedViaAnnotation: constants.CreatedViaAI})
} else {
resourcemerge.MergeMap(modified,
&cluster.Annotations, map[string]string{constants.CreatedViaAnnotation: constants.CreatedViaHive})
}
if !*modified {
return nil
}
// using patch method to avoid error: "the object has been modified; please apply your changes to the
// latest version and try again", see:
// https://github.com/kubernetes-sigs/controller-runtime/issues/1509
// https://github.com/kubernetes-sigs/controller-runtime/issues/741
if err := r.client.Patch(ctx, cluster, patch); err != nil {
return err
}
r.recorder.Eventf("ManagedClusterLabelsUpdated", "The managed cluster %s labels is added", cluster.Name)
return nil
}
func (r *ReconcileClusterDeployment) removeImportFinalizer(
ctx context.Context, clusterDeployment *hivev1.ClusterDeployment) error {
hasImportFinalizer := false
for _, finalizer := range clusterDeployment.Finalizers {
if finalizer == constants.ImportFinalizer {
hasImportFinalizer = true
break
}
}
if !hasImportFinalizer {
// the clusterdeployment does not have import finalizer, ignore it
log.Info("the clusterDeployment does not have import finalizer, skip it",
"clusterDeployment", clusterDeployment.Name)
return nil
}
if len(clusterDeployment.Finalizers) != 1 {
// the clusterdeployment has other finalizers, wait hive to remove them
log.Info("wait hive to remove the finalizers from the clusterdeployment",
"clusterdeployment", clusterDeployment.Name)
return nil
}
patch := client.MergeFrom(clusterDeployment.DeepCopy())
clusterDeployment.Finalizers = []string{}
if err := r.client.Patch(ctx, clusterDeployment, patch); err != nil {
return err
}
r.recorder.Eventf("ClusterDeploymentFinalizerRemoved",
"The clusterdeployment %s finalizer %s is removed", clusterDeployment.Name, constants.ImportFinalizer)
return nil
}