sigs.k8s.io/cluster-api-provider-azure@v1.17.0/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/builder" 43 "sigs.k8s.io/controller-runtime/pkg/client" 44 "sigs.k8s.io/controller-runtime/pkg/controller" 45 "sigs.k8s.io/controller-runtime/pkg/event" 46 "sigs.k8s.io/controller-runtime/pkg/handler" 47 "sigs.k8s.io/controller-runtime/pkg/predicate" 48 "sigs.k8s.io/controller-runtime/pkg/reconcile" 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 return 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 // Add a watch on Clusters to requeue when the infraRef is set. This is needed because the infraRef is not initially 78 // set in Clusters created from a ClusterClass. 79 Watches( 80 &clusterv1.Cluster{}, 81 handler.EnqueueRequestsFromMapFunc(azureMachineMapper), 82 builder.WithPredicates( 83 predicates.ClusterUnpausedAndInfrastructureReady(log), 84 predicates.ResourceNotPausedAndHasFilterLabel(log, r.WatchFilterValue), 85 ), 86 ). 87 Complete(r) 88 } 89 90 type filterUnclonedMachinesPredicate struct { 91 log logr.Logger 92 predicate.Funcs 93 } 94 95 func (f filterUnclonedMachinesPredicate) Create(e event.CreateEvent) bool { 96 return f.Generic(event.GenericEvent(e)) 97 } 98 99 func (f filterUnclonedMachinesPredicate) Update(e event.UpdateEvent) bool { 100 return f.Generic(event.GenericEvent{ 101 Object: e.ObjectNew, 102 }) 103 } 104 105 // Generic implements a default GenericEvent filter. 106 func (f filterUnclonedMachinesPredicate) Generic(e event.GenericEvent) bool { 107 if e.Object == nil { 108 f.log.Error(nil, "Generic event has no old metadata", "event", e) 109 return false 110 } 111 112 // when watching machines, we only care about machines users created one-off 113 // outside of machinedeployments/machinesets and using AzureMachineTemplates. if a machine is part of a machineset 114 // or machinedeployment, we already created a secret for the template. All machines 115 // in the machinedeployment will share that one secret. 116 gvk := infrav1.GroupVersion.WithKind("AzureMachineTemplate") 117 isClonedFromTemplate := e.Object.GetAnnotations()[clusterv1.TemplateClonedFromGroupKindAnnotation] == gvk.GroupKind().String() 118 119 return !isClonedFromTemplate 120 } 121 122 // Reconcile reconciles the Azure json for a specific machine not in a machine deployment. 123 func (r *AzureJSONMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { 124 ctx, cancel := context.WithTimeout(ctx, r.Timeouts.DefaultedLoopTimeout()) 125 defer cancel() 126 127 ctx, log, done := tele.StartSpanWithLogger( 128 ctx, 129 "controllers.AzureJSONMachineReconciler.Reconcile", 130 tele.KVP("namespace", req.Namespace), 131 tele.KVP("name", req.Name), 132 tele.KVP("kind", "AzureMachine"), 133 ) 134 defer done() 135 136 // Fetch the AzureMachine instance 137 azureMachine := &infrav1.AzureMachine{} 138 err := r.Get(ctx, req.NamespacedName, azureMachine) 139 if err != nil { 140 if apierrors.IsNotFound(err) { 141 log.Info("object was not found") 142 return reconcile.Result{}, nil 143 } 144 return reconcile.Result{}, err 145 } 146 147 // Fetch the Cluster. 148 cluster, err := util.GetClusterFromMetadata(ctx, r.Client, azureMachine.ObjectMeta) 149 if err != nil { 150 return reconcile.Result{}, err 151 } 152 if cluster == nil { 153 log.Info("Cluster Controller has not yet set OwnerRef") 154 return reconcile.Result{}, nil 155 } 156 157 log = log.WithValues("cluster", cluster.Name) 158 159 // Return early if the object or Cluster is paused. 160 if annotations.IsPaused(cluster, azureMachine) { 161 log.Info("AzureMachine or linked Cluster is marked as paused. Won't reconcile") 162 return ctrl.Result{}, nil 163 } 164 165 _, kind := infrav1.GroupVersion.WithKind(infrav1.AzureClusterKind).ToAPIVersionAndKind() 166 167 // only look at azure clusters 168 if cluster.Spec.InfrastructureRef == nil { 169 log.Info("infra ref is nil") 170 return ctrl.Result{}, nil 171 } 172 if cluster.Spec.InfrastructureRef.Kind != kind { 173 log.WithValues("kind", cluster.Spec.InfrastructureRef.Kind).Info("infra ref was not an AzureCluster") 174 return ctrl.Result{}, nil 175 } 176 177 // fetch the corresponding azure cluster 178 azureCluster := &infrav1.AzureCluster{} 179 azureClusterName := types.NamespacedName{ 180 Namespace: req.Namespace, 181 Name: cluster.Spec.InfrastructureRef.Name, 182 } 183 184 if err := r.Get(ctx, azureClusterName, azureCluster); err != nil { 185 log.Error(err, "failed to fetch AzureCluster") 186 return reconcile.Result{}, err 187 } 188 189 // Create the scope. 190 clusterScope, err := scope.NewClusterScope(ctx, scope.ClusterScopeParams{ 191 Client: r.Client, 192 Cluster: cluster, 193 AzureCluster: azureCluster, 194 Timeouts: r.Timeouts, 195 }) 196 if err != nil { 197 return reconcile.Result{}, errors.Wrap(err, "failed to create scope") 198 } 199 200 apiVersion, kind := infrav1.GroupVersion.WithKind("AzureMachine").ToAPIVersionAndKind() 201 owner := metav1.OwnerReference{ 202 APIVersion: apiVersion, 203 Kind: kind, 204 Name: azureMachine.GetName(), 205 UID: azureMachine.GetUID(), 206 Controller: ptr.To(true), 207 } 208 209 // Construct secret for this machine 210 userAssignedIdentityIfExists := "" 211 if len(azureMachine.Spec.UserAssignedIdentities) > 0 { 212 var identitiesClient identities.Client 213 identitiesClient, err := identities.NewClient(clusterScope) 214 if err != nil { 215 return reconcile.Result{}, errors.Wrap(err, "failed to create identities client") 216 } 217 parsed, err := azureutil.ParseResourceID(azureMachine.Spec.UserAssignedIdentities[0].ProviderID) 218 if err != nil { 219 return reconcile.Result{}, errors.Wrapf(err, "failed to parse ProviderID %s", azureMachine.Spec.UserAssignedIdentities[0].ProviderID) 220 } 221 if parsed.SubscriptionID != clusterScope.SubscriptionID() { 222 identitiesClient, err = identities.NewClientBySub(clusterScope, parsed.SubscriptionID) 223 if err != nil { 224 return reconcile.Result{}, errors.Wrapf(err, "failed to create identities client from subscription ID %s", parsed.SubscriptionID) 225 } 226 } 227 userAssignedIdentityIfExists, err = identitiesClient.GetClientID( 228 ctx, azureMachine.Spec.UserAssignedIdentities[0].ProviderID) 229 if err != nil { 230 return reconcile.Result{}, errors.Wrap(err, "failed to get user-assigned identity ClientID") 231 } 232 } 233 234 if azureMachine.Spec.Identity == infrav1.VMIdentityNone { 235 log.Info(fmt.Sprintf("WARNING, %s", spIdentityWarning)) 236 r.Recorder.Eventf(azureMachine, corev1.EventTypeWarning, "VMIdentityNone", spIdentityWarning) 237 } 238 239 newSecret, err := GetCloudProviderSecret( 240 clusterScope, 241 azureMachine.Namespace, 242 azureMachine.Name, 243 owner, 244 azureMachine.Spec.Identity, 245 userAssignedIdentityIfExists, 246 ) 247 248 if err != nil { 249 return ctrl.Result{}, errors.Wrap(err, "failed to create cloud provider config") 250 } 251 252 if err := reconcileAzureSecret(ctx, r.Client, owner, newSecret, clusterScope.ClusterName()); err != nil { 253 r.Recorder.Eventf(azureMachine, corev1.EventTypeWarning, "Error reconciling cloud provider secret for AzureMachine", err.Error()) 254 return ctrl.Result{}, errors.Wrap(err, "failed to reconcile azure secret") 255 } 256 257 return ctrl.Result{}, nil 258 }