sigs.k8s.io/cluster-api-provider-azure@v1.14.3/controllers/azurejson_machine_controller.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package controllers 18 19 import ( 20 "context" 21 "fmt" 22 23 "github.com/go-logr/logr" 24 "github.com/pkg/errors" 25 corev1 "k8s.io/api/core/v1" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/tools/record" 30 "k8s.io/utils/ptr" 31 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 32 "sigs.k8s.io/cluster-api-provider-azure/azure/scope" 33 "sigs.k8s.io/cluster-api-provider-azure/azure/services/identities" 34 azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure" 35 "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" 36 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 37 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 38 "sigs.k8s.io/cluster-api/util" 39 "sigs.k8s.io/cluster-api/util/annotations" 40 "sigs.k8s.io/cluster-api/util/predicates" 41 ctrl "sigs.k8s.io/controller-runtime" 42 "sigs.k8s.io/controller-runtime/pkg/client" 43 "sigs.k8s.io/controller-runtime/pkg/controller" 44 "sigs.k8s.io/controller-runtime/pkg/event" 45 "sigs.k8s.io/controller-runtime/pkg/handler" 46 "sigs.k8s.io/controller-runtime/pkg/predicate" 47 "sigs.k8s.io/controller-runtime/pkg/reconcile" 48 "sigs.k8s.io/controller-runtime/pkg/source" 49 ) 50 51 // AzureJSONMachineReconciler reconciles Azure json secrets for AzureMachine objects. 52 type AzureJSONMachineReconciler struct { 53 client.Client 54 Recorder record.EventRecorder 55 Timeouts reconciler.Timeouts 56 WatchFilterValue string 57 } 58 59 // SetupWithManager initializes this controller with a manager. 60 func (r *AzureJSONMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { 61 _, log, done := tele.StartSpanWithLogger(ctx, 62 "controllers.AzureJSONMachineReconciler.SetupWithManager", 63 ) 64 defer done() 65 66 azureMachineMapper, err := util.ClusterToTypedObjectsMapper(r.Client, &infrav1.AzureMachineList{}, mgr.GetScheme()) 67 if err != nil { 68 return errors.Wrap(err, "failed to create mapper for Cluster to AzureMachines") 69 } 70 71 c, err := ctrl.NewControllerManagedBy(mgr). 72 WithOptions(options). 73 For(&infrav1.AzureMachine{}). 74 WithEventFilter(filterUnclonedMachinesPredicate{log: log}). 75 WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue)). 76 Owns(&corev1.Secret{}). 77 Build(r) 78 79 if err != nil { 80 return errors.Wrap(err, "failed to create controller") 81 } 82 83 // Add a watch on Clusters to requeue when the infraRef is set. This is needed because the infraRef is not initially 84 // set in Clusters created from a ClusterClass. 85 if err := c.Watch( 86 source.Kind(mgr.GetCache(), &clusterv1.Cluster{}), 87 handler.EnqueueRequestsFromMapFunc(azureMachineMapper), 88 predicates.ClusterUnpausedAndInfrastructureReady(log), 89 predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue), 90 ); err != nil { 91 return errors.Wrap(err, "failed adding a watch for Clusters") 92 } 93 94 return nil 95 } 96 97 type filterUnclonedMachinesPredicate struct { 98 log logr.Logger 99 predicate.Funcs 100 } 101 102 func (f filterUnclonedMachinesPredicate) Create(e event.CreateEvent) bool { 103 return f.Generic(event.GenericEvent(e)) 104 } 105 106 func (f filterUnclonedMachinesPredicate) Update(e event.UpdateEvent) bool { 107 return f.Generic(event.GenericEvent{ 108 Object: e.ObjectNew, 109 }) 110 } 111 112 // Generic implements a default GenericEvent filter. 113 func (f filterUnclonedMachinesPredicate) Generic(e event.GenericEvent) bool { 114 if e.Object == nil { 115 f.log.Error(nil, "Generic event has no old metadata", "event", e) 116 return false 117 } 118 119 // when watching machines, we only care about machines users created one-off 120 // outside of machinedeployments/machinesets and using AzureMachineTemplates. if a machine is part of a machineset 121 // or machinedeployment, we already created a secret for the template. All machines 122 // in the machinedeployment will share that one secret. 123 gvk := infrav1.GroupVersion.WithKind("AzureMachineTemplate") 124 isClonedFromTemplate := e.Object.GetAnnotations()[clusterv1.TemplateClonedFromGroupKindAnnotation] == gvk.GroupKind().String() 125 126 return !isClonedFromTemplate 127 } 128 129 // Reconcile reconciles the Azure json for a specific machine not in a machine deployment. 130 func (r *AzureJSONMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { 131 ctx, cancel := context.WithTimeout(ctx, r.Timeouts.DefaultedLoopTimeout()) 132 defer cancel() 133 134 ctx, log, done := tele.StartSpanWithLogger( 135 ctx, 136 "controllers.AzureJSONMachineReconciler.Reconcile", 137 tele.KVP("namespace", req.Namespace), 138 tele.KVP("name", req.Name), 139 tele.KVP("kind", "AzureMachine"), 140 ) 141 defer done() 142 143 // Fetch the AzureMachine instance 144 azureMachine := &infrav1.AzureMachine{} 145 err := r.Get(ctx, req.NamespacedName, azureMachine) 146 if err != nil { 147 if apierrors.IsNotFound(err) { 148 log.Info("object was not found") 149 return reconcile.Result{}, nil 150 } 151 return reconcile.Result{}, err 152 } 153 154 // Fetch the Cluster. 155 cluster, err := util.GetClusterFromMetadata(ctx, r.Client, azureMachine.ObjectMeta) 156 if err != nil { 157 return reconcile.Result{}, err 158 } 159 if cluster == nil { 160 log.Info("Cluster Controller has not yet set OwnerRef") 161 return reconcile.Result{}, nil 162 } 163 164 log = log.WithValues("cluster", cluster.Name) 165 166 // Return early if the object or Cluster is paused. 167 if annotations.IsPaused(cluster, azureMachine) { 168 log.Info("AzureMachine or linked Cluster is marked as paused. Won't reconcile") 169 return ctrl.Result{}, nil 170 } 171 172 _, kind := infrav1.GroupVersion.WithKind(infrav1.AzureClusterKind).ToAPIVersionAndKind() 173 174 // only look at azure clusters 175 if cluster.Spec.InfrastructureRef == nil { 176 log.Info("infra ref is nil") 177 return ctrl.Result{}, nil 178 } 179 if cluster.Spec.InfrastructureRef.Kind != kind { 180 log.WithValues("kind", cluster.Spec.InfrastructureRef.Kind).Info("infra ref was not an AzureCluster") 181 return ctrl.Result{}, nil 182 } 183 184 // fetch the corresponding azure cluster 185 azureCluster := &infrav1.AzureCluster{} 186 azureClusterName := types.NamespacedName{ 187 Namespace: req.Namespace, 188 Name: cluster.Spec.InfrastructureRef.Name, 189 } 190 191 if err := r.Get(ctx, azureClusterName, azureCluster); err != nil { 192 log.Error(err, "failed to fetch AzureCluster") 193 return reconcile.Result{}, err 194 } 195 196 // Create the scope. 197 clusterScope, err := scope.NewClusterScope(ctx, scope.ClusterScopeParams{ 198 Client: r.Client, 199 Cluster: cluster, 200 AzureCluster: azureCluster, 201 Timeouts: r.Timeouts, 202 }) 203 if err != nil { 204 return reconcile.Result{}, errors.Wrap(err, "failed to create scope") 205 } 206 207 apiVersion, kind := infrav1.GroupVersion.WithKind("AzureMachine").ToAPIVersionAndKind() 208 owner := metav1.OwnerReference{ 209 APIVersion: apiVersion, 210 Kind: kind, 211 Name: azureMachine.GetName(), 212 UID: azureMachine.GetUID(), 213 Controller: ptr.To(true), 214 } 215 216 // Construct secret for this machine 217 userAssignedIdentityIfExists := "" 218 if len(azureMachine.Spec.UserAssignedIdentities) > 0 { 219 var identitiesClient identities.Client 220 identitiesClient, err := identities.NewClient(clusterScope) 221 if err != nil { 222 return reconcile.Result{}, errors.Wrap(err, "failed to create identities client") 223 } 224 parsed, err := azureutil.ParseResourceID(azureMachine.Spec.UserAssignedIdentities[0].ProviderID) 225 if err != nil { 226 return reconcile.Result{}, errors.Wrapf(err, "failed to parse ProviderID %s", azureMachine.Spec.UserAssignedIdentities[0].ProviderID) 227 } 228 if parsed.SubscriptionID != clusterScope.SubscriptionID() { 229 identitiesClient, err = identities.NewClientBySub(clusterScope, parsed.SubscriptionID) 230 if err != nil { 231 return reconcile.Result{}, errors.Wrapf(err, "failed to create identities client from subscription ID %s", parsed.SubscriptionID) 232 } 233 } 234 userAssignedIdentityIfExists, err = identitiesClient.GetClientID( 235 ctx, azureMachine.Spec.UserAssignedIdentities[0].ProviderID) 236 if err != nil { 237 return reconcile.Result{}, errors.Wrap(err, "failed to get user-assigned identity ClientID") 238 } 239 } 240 241 if azureMachine.Spec.Identity == infrav1.VMIdentityNone { 242 log.Info(fmt.Sprintf("WARNING, %s", spIdentityWarning)) 243 r.Recorder.Eventf(azureMachine, corev1.EventTypeWarning, "VMIdentityNone", spIdentityWarning) 244 } 245 246 newSecret, err := GetCloudProviderSecret( 247 clusterScope, 248 azureMachine.Namespace, 249 azureMachine.Name, 250 owner, 251 azureMachine.Spec.Identity, 252 userAssignedIdentityIfExists, 253 ) 254 255 if err != nil { 256 return ctrl.Result{}, errors.Wrap(err, "failed to create cloud provider config") 257 } 258 259 if err := reconcileAzureSecret(ctx, r.Client, owner, newSecret, clusterScope.ClusterName()); err != nil { 260 r.Recorder.Eventf(azureMachine, corev1.EventTypeWarning, "Error reconciling cloud provider secret for AzureMachine", err.Error()) 261 return ctrl.Result{}, errors.Wrap(err, "failed to reconcile azure secret") 262 } 263 264 return ctrl.Result{}, nil 265 }