github.com/Azure/aad-pod-identity@v1.8.17/pkg/crd/crd.go (about) 1 package crd 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strings" 9 "time" 10 11 aadpodid "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity" 12 aadpodv1 "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity/v1" 13 "github.com/Azure/aad-pod-identity/pkg/metrics" 14 "github.com/Azure/aad-pod-identity/pkg/stats" 15 16 apierrors "k8s.io/apimachinery/pkg/api/errors" 17 "k8s.io/apimachinery/pkg/api/meta" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/fields" 21 "k8s.io/apimachinery/pkg/runtime" 22 "k8s.io/apimachinery/pkg/runtime/schema" 23 "k8s.io/apimachinery/pkg/runtime/serializer" 24 "k8s.io/apimachinery/pkg/types" 25 "k8s.io/client-go/informers/internalinterfaces" 26 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 27 "k8s.io/client-go/rest" 28 "k8s.io/client-go/tools/cache" 29 "k8s.io/klog/v2" 30 ) 31 32 const ( 33 finalizerName = "azureassignedidentity.finalizers.aadpodidentity.k8s.io" 34 ) 35 36 // Client represents all the watchers 37 type Client struct { 38 rest *rest.RESTClient 39 BindingInformer cache.SharedInformer 40 IDInformer cache.SharedInformer 41 AssignedIDInformer cache.SharedInformer 42 PodIdentityExceptionInformer cache.SharedInformer 43 reporter *metrics.Reporter 44 } 45 46 // ClientInt is an abstraction used to interact with CRDs. 47 type ClientInt interface { 48 Start(exit <-chan struct{}) 49 SyncCache(exit <-chan struct{}, initial bool, cacheSyncs ...cache.InformerSynced) 50 SyncCacheAll(exit <-chan struct{}, initial bool) 51 RemoveAssignedIdentity(assignedIdentity *aadpodid.AzureAssignedIdentity) error 52 CreateAssignedIdentity(assignedIdentity *aadpodid.AzureAssignedIdentity) error 53 UpdateAssignedIdentity(assignedIdentity *aadpodid.AzureAssignedIdentity) error 54 UpdateAzureAssignedIdentityStatus(assignedIdentity *aadpodid.AzureAssignedIdentity, status string) error 55 UpgradeAll() error 56 ListBindings() (res *[]aadpodid.AzureIdentityBinding, err error) 57 ListAssignedIDs() (res *[]aadpodid.AzureAssignedIdentity, err error) 58 ListAssignedIDsInMap() (res map[string]aadpodid.AzureAssignedIdentity, err error) 59 ListIds() (res *[]aadpodid.AzureIdentity, err error) 60 ListPodIds(podns, podname string) (map[string][]aadpodid.AzureIdentity, error) 61 ListPodIdentityExceptions(ns string) (res *[]aadpodid.AzurePodIdentityException, err error) 62 } 63 64 // NewCRDClientLite returns a new CRD lite client and error if any. 65 func NewCRDClientLite(config *rest.Config, nodeName string, scale, isStandardMode bool) (*Client, error) { 66 restClient, err := newRestClient(config) 67 if err != nil { 68 return nil, err 69 } 70 71 var assignedIDListInformer, bindingListInformer, idListInformer cache.SharedInformer 72 73 // assigned identity informer is required only for standard mode 74 if isStandardMode { 75 var assignedIDListWatch *cache.ListWatch 76 if scale { 77 assignedIDListWatch = newAssignedIDNodeListWatch(restClient, nodeName) 78 } else { 79 assignedIDListWatch = newAssignedIDListWatch(restClient) 80 } 81 82 assignedIDListInformer, err = newAssignedIDInformer(assignedIDListWatch) 83 if err != nil { 84 return nil, err 85 } 86 } else { 87 // creating binding and identity list informers for non standard mode 88 if bindingListInformer, err = newBindingInformerLite(newBindingListWatch(restClient)); err != nil { 89 return nil, err 90 } 91 if idListInformer, err = newIDInformerLite(newIDListWatch(restClient)); err != nil { 92 return nil, err 93 } 94 } 95 podIdentityExceptionListWatch := newPodIdentityExceptionListWatch(restClient) 96 podIdentityExceptionInformer, err := newPodIdentityExceptionInformer(podIdentityExceptionListWatch) 97 if err != nil { 98 return nil, err 99 } 100 101 reporter, err := metrics.NewReporter() 102 if err != nil { 103 return nil, fmt.Errorf("failed to create reporter for metrics, error: %+v", err) 104 } 105 106 return &Client{ 107 AssignedIDInformer: assignedIDListInformer, 108 PodIdentityExceptionInformer: podIdentityExceptionInformer, 109 BindingInformer: bindingListInformer, 110 IDInformer: idListInformer, 111 rest: restClient, 112 reporter: reporter, 113 }, nil 114 } 115 116 // NewCRDClient returns a new CRD client and error if any. 117 func NewCRDClient(config *rest.Config, eventCh chan aadpodid.EventType) (*Client, error) { 118 restClient, err := newRestClient(config) 119 if err != nil { 120 return nil, err 121 } 122 123 bindingListWatch := newBindingListWatch(restClient) 124 bindingInformer, err := newBindingInformer(restClient, eventCh, bindingListWatch) 125 if err != nil { 126 return nil, err 127 } 128 129 idListWatch := newIDListWatch(restClient) 130 idInformer, err := newIDInformer(restClient, eventCh, idListWatch) 131 if err != nil { 132 return nil, err 133 } 134 135 assignedIDListWatch := newAssignedIDListWatch(restClient) 136 assignedIDListInformer, err := newAssignedIDInformer(assignedIDListWatch) 137 if err != nil { 138 return nil, err 139 } 140 141 reporter, err := metrics.NewReporter() 142 if err != nil { 143 return nil, fmt.Errorf("failed to create reporter for metrics, error: %+v", err) 144 } 145 146 return &Client{ 147 rest: restClient, 148 BindingInformer: bindingInformer, 149 IDInformer: idInformer, 150 AssignedIDInformer: assignedIDListInformer, 151 reporter: reporter, 152 }, nil 153 } 154 155 func newRestClient(config *rest.Config) (*rest.RESTClient, error) { 156 crdconfig := *config 157 crdconfig.GroupVersion = &aadpodv1.SchemeGroupVersion 158 crdconfig.APIPath = "/apis" 159 crdconfig.ContentType = runtime.ContentTypeJSON 160 scheme := runtime.NewScheme() 161 162 if err := aadpodv1.AddToScheme(scheme); err != nil { 163 return nil, err 164 } 165 if err := clientgoscheme.AddToScheme(scheme); err != nil { 166 return nil, err 167 } 168 169 crdconfig.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)} 170 171 // Client interacting with our CRDs 172 restClient, err := rest.RESTClientFor(&crdconfig) 173 if err != nil { 174 return nil, err 175 } 176 return restClient, nil 177 } 178 179 func newBindingListWatch(r *rest.RESTClient) *cache.ListWatch { 180 return cache.NewListWatchFromClient(r, aadpodv1.AzureIDBindingResource, v1.NamespaceAll, fields.Everything()) 181 } 182 183 func newBindingInformer(r *rest.RESTClient, eventCh chan aadpodid.EventType, lw *cache.ListWatch) (cache.SharedInformer, error) { 184 azBindingInformer := cache.NewSharedInformer( 185 lw, 186 &aadpodv1.AzureIdentityBinding{}, 187 time.Minute*10) 188 if azBindingInformer == nil { 189 return nil, fmt.Errorf("failed to create watcher for %s", aadpodv1.AzureIDBindingResource) 190 } 191 azBindingInformer.AddEventHandler( 192 cache.ResourceEventHandlerFuncs{ 193 AddFunc: func(obj interface{}) { 194 klog.V(6).Infof("binding created") 195 eventCh <- aadpodid.BindingCreated 196 }, 197 DeleteFunc: func(obj interface{}) { 198 klog.V(6).Infof("binding deleted") 199 eventCh <- aadpodid.BindingDeleted 200 }, 201 UpdateFunc: func(OldObj, newObj interface{}) { 202 klog.V(6).Infof("binding updated") 203 eventCh <- aadpodid.BindingUpdated 204 }, 205 }, 206 ) 207 return azBindingInformer, nil 208 } 209 210 func newIDListWatch(r *rest.RESTClient) *cache.ListWatch { 211 return cache.NewListWatchFromClient(r, aadpodv1.AzureIDResource, v1.NamespaceAll, fields.Everything()) 212 } 213 214 func newIDInformer(r *rest.RESTClient, eventCh chan aadpodid.EventType, lw *cache.ListWatch) (cache.SharedInformer, error) { 215 azIDInformer := cache.NewSharedInformer( 216 lw, 217 &aadpodv1.AzureIdentity{}, 218 time.Minute*10) 219 if azIDInformer == nil { 220 return nil, fmt.Errorf("failed to create watcher for %s", aadpodv1.AzureIDResource) 221 } 222 azIDInformer.AddEventHandler( 223 cache.ResourceEventHandlerFuncs{ 224 AddFunc: func(obj interface{}) { 225 klog.V(6).Infof("identity created") 226 eventCh <- aadpodid.IdentityCreated 227 }, 228 DeleteFunc: func(obj interface{}) { 229 klog.V(6).Infof("identity deleted") 230 eventCh <- aadpodid.IdentityDeleted 231 }, 232 UpdateFunc: func(OldObj, newObj interface{}) { 233 klog.V(6).Infof("identity updated") 234 eventCh <- aadpodid.IdentityUpdated 235 }, 236 }, 237 ) 238 return azIDInformer, nil 239 } 240 241 // NodeNameFilter - CRDs do not yet support field selectors. Instead of that we 242 // apply labels with node name and then later use the NodeNameFilter to tweak 243 // options to filter using nodename label. 244 func NodeNameFilter(nodeName string) internalinterfaces.TweakListOptionsFunc { 245 return func(l *v1.ListOptions) { 246 if l == nil { 247 l = &v1.ListOptions{} 248 } 249 l.LabelSelector = l.LabelSelector + "nodename=" + nodeName 250 } 251 } 252 253 func newAssignedIDNodeListWatch(r *rest.RESTClient, nodeName string) *cache.ListWatch { 254 return cache.NewFilteredListWatchFromClient(r, aadpodv1.AzureAssignedIDResource, v1.NamespaceAll, NodeNameFilter(nodeName)) 255 } 256 257 func newAssignedIDListWatch(r *rest.RESTClient) *cache.ListWatch { 258 return cache.NewListWatchFromClient(r, aadpodv1.AzureAssignedIDResource, v1.NamespaceAll, fields.Everything()) 259 } 260 261 func newAssignedIDInformer(lw *cache.ListWatch) (cache.SharedInformer, error) { 262 azAssignedIDInformer := cache.NewSharedInformer(lw, &aadpodv1.AzureAssignedIdentity{}, time.Minute*10) 263 if azAssignedIDInformer == nil { 264 return nil, fmt.Errorf("failed to create %s informer", aadpodv1.AzureAssignedIDResource) 265 } 266 return azAssignedIDInformer, nil 267 } 268 269 func newBindingInformerLite(lw *cache.ListWatch) (cache.SharedInformer, error) { 270 azBindingInformer := cache.NewSharedInformer(lw, &aadpodv1.AzureIdentityBinding{}, time.Minute*10) 271 if azBindingInformer == nil { 272 return nil, fmt.Errorf("failed to create %s informer", aadpodv1.AzureIDBindingResource) 273 } 274 return azBindingInformer, nil 275 } 276 277 func newIDInformerLite(lw *cache.ListWatch) (cache.SharedInformer, error) { 278 azIDInformer := cache.NewSharedInformer(lw, &aadpodv1.AzureIdentity{}, time.Minute*10) 279 if azIDInformer == nil { 280 return nil, fmt.Errorf("failed to create %s informer", aadpodv1.AzureIDResource) 281 } 282 return azIDInformer, nil 283 } 284 285 func newPodIdentityExceptionListWatch(r *rest.RESTClient) *cache.ListWatch { 286 optionsModifier := func(options *v1.ListOptions) {} 287 return cache.NewFilteredListWatchFromClient( 288 r, 289 aadpodv1.AzurePodIdentityExceptionResource, 290 v1.NamespaceAll, 291 optionsModifier, 292 ) 293 } 294 295 func newPodIdentityExceptionInformer(lw *cache.ListWatch) (cache.SharedInformer, error) { 296 azPodIDExceptionInformer := cache.NewSharedInformer(lw, &aadpodv1.AzurePodIdentityException{}, time.Minute*10) 297 if azPodIDExceptionInformer == nil { 298 return nil, fmt.Errorf("failed to create %s informer", aadpodv1.AzurePodIdentityExceptionResource) 299 } 300 return azPodIDExceptionInformer, nil 301 } 302 303 func (c *Client) getObjectList(resource string, i runtime.Object) (runtime.Object, error) { 304 options := v1.ListOptions{} 305 do := c.rest.Get().Namespace(v1.NamespaceAll).Resource(resource).VersionedParams(&options, v1.ParameterCodec).Do(context.TODO()) 306 body, err := do.Raw() 307 if err != nil { 308 return nil, fmt.Errorf("failed to get %s, error: %+v", resource, err) 309 } 310 err = json.Unmarshal(body, &i) 311 if err != nil { 312 return nil, fmt.Errorf("failed to unmarshal to object %T, error: %+v", i, err) 313 } 314 return i, err 315 } 316 317 func (c *Client) setObject(resource, ns, name string, i interface{}, obj runtime.Object) error { 318 err := c.rest.Put().Namespace(ns).Resource(resource).Name(name).Body(i).Do(context.TODO()).Into(obj) 319 if err != nil { 320 return fmt.Errorf("failed to set object for resource %s, error: %+v", resource, err) 321 } 322 return nil 323 } 324 325 // Upgrade performs type upgrade to a specific aad-pod-identity CRD. 326 func (c *Client) Upgrade(resource string, i runtime.Object) (map[string]runtime.Object, error) { 327 m := make(map[string]runtime.Object) 328 i, err := c.getObjectList(resource, i) 329 if err != nil { 330 return m, err 331 } 332 333 list, err := meta.ExtractList(i) 334 if err != nil { 335 return m, fmt.Errorf("failed to extract list for resource %s, error: %+v", resource, err) 336 } 337 338 for _, item := range list { 339 o, err := meta.Accessor(item) 340 if err != nil { 341 return m, fmt.Errorf("failed to get object for resource %s, error: %+v", resource, err) 342 } 343 switch resource { 344 case aadpodv1.AzureIDResource: 345 var obj aadpodv1.AzureIdentity 346 err = c.setObject(resource, o.GetNamespace(), o.GetName(), o, &obj) 347 if err != nil { 348 return m, err 349 } 350 obj.TypeMeta = metav1.TypeMeta{ 351 APIVersion: aadpodv1.SchemeGroupVersion.String(), 352 Kind: reflect.ValueOf(i).Elem().Type().Name(), 353 } 354 m[getMapKey(o.GetNamespace(), o.GetName())] = &obj 355 case aadpodv1.AzureIDBindingResource: 356 var obj aadpodv1.AzureIdentityBinding 357 err = c.setObject(resource, o.GetNamespace(), o.GetName(), o, &obj) 358 if err != nil { 359 return m, err 360 } 361 obj.TypeMeta = metav1.TypeMeta{ 362 APIVersion: aadpodv1.SchemeGroupVersion.String(), 363 Kind: reflect.ValueOf(i).Elem().Type().Name(), 364 } 365 m[getMapKey(o.GetNamespace(), o.GetName())] = &obj 366 default: 367 err = c.setObject(resource, o.GetNamespace(), o.GetName(), o, nil) 368 if err != nil { 369 return m, err 370 } 371 } 372 } 373 return m, nil 374 } 375 376 // UpgradeAll performs type upgrade to for all aad-pod-identity CRDs. 377 func (c *Client) UpgradeAll() error { 378 updatedAzureIdentities, err := c.Upgrade(aadpodv1.AzureIDResource, &aadpodv1.AzureIdentityList{}) 379 if err != nil { 380 return err 381 } 382 updatedAzureIdentityBindings, err := c.Upgrade(aadpodv1.AzureIDBindingResource, &aadpodv1.AzureIdentityBindingList{}) 383 if err != nil { 384 return err 385 } 386 _, err = c.Upgrade(aadpodv1.AzurePodIdentityExceptionResource, &aadpodv1.AzurePodIdentityExceptionList{}) 387 if err != nil { 388 return err 389 } 390 391 // update azure assigned identities separately as we need to use the latest 392 // updated azure identity and binding as ref. Doing this will ensure upgrade does 393 // not trigger any sync cycles 394 i, err := c.getObjectList(aadpodv1.AzureAssignedIDResource, &aadpodv1.AzureAssignedIdentityList{}) 395 if err != nil { 396 return err 397 } 398 list, err := meta.ExtractList(i) 399 if err != nil { 400 return fmt.Errorf("failed to extract list for resource: %s, error: %+v", aadpodv1.AzureAssignedIDResource, err) 401 } 402 for _, item := range list { 403 o, err := meta.Accessor(item) 404 if err != nil { 405 return fmt.Errorf("failed to get object for resource: %s, error: %+v", aadpodv1.AzureAssignedIDResource, err) 406 } 407 obj := o.(*aadpodv1.AzureAssignedIdentity) 408 idName := obj.Spec.AzureIdentityRef.Name 409 idNamespace := obj.Spec.AzureIdentityRef.Namespace 410 bindingName := obj.Spec.AzureBindingRef.Name 411 bindingNamespace := obj.Spec.AzureBindingRef.Namespace 412 413 if v, exists := updatedAzureIdentities[getMapKey(idNamespace, idName)]; exists && v != nil { 414 obj.Spec.AzureIdentityRef = v.(*aadpodv1.AzureIdentity) 415 } 416 if v, exists := updatedAzureIdentityBindings[getMapKey(bindingNamespace, bindingName)]; exists && v != nil { 417 obj.Spec.AzureBindingRef = v.(*aadpodv1.AzureIdentityBinding) 418 } 419 err = c.setObject(aadpodv1.AzureAssignedIDResource, o.GetNamespace(), o.GetName(), obj, nil) 420 if err != nil { 421 return err 422 } 423 } 424 return nil 425 } 426 427 // StartLite to be used only case of lite client 428 func (c *Client) StartLite(exit <-chan struct{}) { 429 var cacheHasSynced []cache.InformerSynced 430 431 if c.AssignedIDInformer != nil { 432 go c.AssignedIDInformer.Run(exit) 433 cacheHasSynced = append(cacheHasSynced, c.AssignedIDInformer.HasSynced) 434 } 435 if c.BindingInformer != nil { 436 go c.BindingInformer.Run(exit) 437 cacheHasSynced = append(cacheHasSynced, c.BindingInformer.HasSynced) 438 } 439 if c.IDInformer != nil { 440 go c.IDInformer.Run(exit) 441 cacheHasSynced = append(cacheHasSynced, c.IDInformer.HasSynced) 442 } 443 if c.PodIdentityExceptionInformer != nil { 444 go c.PodIdentityExceptionInformer.Run(exit) 445 cacheHasSynced = append(cacheHasSynced, c.PodIdentityExceptionInformer.HasSynced) 446 } 447 c.SyncCache(exit, true, cacheHasSynced...) 448 klog.Info("CRD lite informers started ") 449 } 450 451 // Start starts all informer routines to watch for CRD-related changes. 452 func (c *Client) Start(exit <-chan struct{}) { 453 go c.BindingInformer.Run(exit) 454 go c.IDInformer.Run(exit) 455 go c.AssignedIDInformer.Run(exit) 456 c.SyncCache(exit, true, c.BindingInformer.HasSynced, c.IDInformer.HasSynced, c.AssignedIDInformer.HasSynced) 457 klog.Info("CRD informers started") 458 } 459 460 // SyncCache synchronizes cache 461 func (c *Client) SyncCache(exit <-chan struct{}, initial bool, cacheSyncs ...cache.InformerSynced) { 462 if !cache.WaitForCacheSync(exit, cacheSyncs...) { 463 if !initial { 464 klog.Errorf("cache failed to be synchronized") 465 return 466 } 467 panic("Cache failed to be synchronized") 468 } 469 } 470 471 // SyncCacheAll - sync all caches related to the client. 472 func (c *Client) SyncCacheAll(exit <-chan struct{}, initial bool) { 473 c.SyncCache(exit, initial, c.BindingInformer.HasSynced, c.IDInformer.HasSynced, c.AssignedIDInformer.HasSynced) 474 } 475 476 // RemoveAssignedIdentity removes the assigned identity 477 func (c *Client) RemoveAssignedIdentity(assignedIdentity *aadpodid.AzureAssignedIdentity) (err error) { 478 klog.V(6).Infof("deleting assigned id %s/%s", assignedIdentity.Namespace, assignedIdentity.Name) 479 begin := time.Now() 480 defer func() { 481 if err != nil { 482 merr := c.reporter.ReportKubernetesAPIOperationError(metrics.AssignedIdentityDeletionOperationName) 483 if merr != nil { 484 klog.Warningf("failed to report metrics, error: %+v", merr) 485 } 486 return 487 } 488 c.reporter.Report( 489 metrics.AssignedIdentityDeletionCountM.M(1), 490 metrics.AssignedIdentityDeletionDurationM.M(metrics.SinceInSeconds(begin))) 491 }() 492 493 var res aadpodv1.AzureAssignedIdentity 494 err = c.rest.Delete().Namespace(assignedIdentity.Namespace).Resource(aadpodid.AzureAssignedIDResource).Name(assignedIdentity.Name).Do(context.TODO()).Into(&res) 495 if apierrors.IsNotFound(err) { 496 return nil 497 } 498 if err != nil { 499 return err 500 } 501 if hasFinalizer(&res) { 502 removeFinalizer(&res) 503 // update the assigned identity without finalizer and resource will be garbage collected 504 err = c.rest.Put().Namespace(assignedIdentity.Namespace).Resource(aadpodid.AzureAssignedIDResource).Name(assignedIdentity.Name).Body(&res).Do(context.TODO()).Error() 505 } 506 507 klog.V(5).Infof("deleting %s took: %v", assignedIdentity.Name, time.Since(begin)) 508 stats.AggregateConcurrent(stats.DeleteAzureAssignedIdentity, begin, time.Now()) 509 return err 510 } 511 512 // CreateAssignedIdentity creates new assigned identity 513 func (c *Client) CreateAssignedIdentity(assignedIdentity *aadpodid.AzureAssignedIdentity) (err error) { 514 klog.Infof("creating assigned id %s/%s", assignedIdentity.Namespace, assignedIdentity.Name) 515 begin := time.Now() 516 defer func() { 517 if err != nil { 518 merr := c.reporter.ReportKubernetesAPIOperationError(metrics.AssignedIdentityAdditionOperationName) 519 if merr != nil { 520 klog.Warningf("failed to report metrics, error: %+v", merr) 521 } 522 return 523 } 524 c.reporter.Report( 525 metrics.AssignedIdentityAdditionCountM.M(1), 526 metrics.AssignedIdentityAdditionDurationM.M(metrics.SinceInSeconds(begin))) 527 }() 528 529 var res aadpodv1.AzureAssignedIdentity 530 v1AssignedID := aadpodv1.ConvertInternalAssignedIdentityToV1AssignedIdentity(*assignedIdentity) 531 if !hasFinalizer(&v1AssignedID) { 532 v1AssignedID.SetFinalizers(append(v1AssignedID.GetFinalizers(), finalizerName)) 533 } 534 err = c.rest.Post().Namespace(assignedIdentity.Namespace).Resource(aadpodid.AzureAssignedIDResource).Body(&v1AssignedID).Do(context.TODO()).Into(&res) 535 if err != nil { 536 return err 537 } 538 539 klog.V(5).Infof("time taken to create %s/%s: %v", assignedIdentity.Namespace, assignedIdentity.Name, time.Since(begin)) 540 stats.AggregateConcurrent(stats.CreateAzureAssignedIdentity, begin, time.Now()) 541 return nil 542 } 543 544 // UpdateAssignedIdentity updates an existing assigned identity 545 func (c *Client) UpdateAssignedIdentity(assignedIdentity *aadpodid.AzureAssignedIdentity) (err error) { 546 klog.Infof("updating assigned id %s/%s", assignedIdentity.Namespace, assignedIdentity.Name) 547 begin := time.Now() 548 defer func() { 549 if err != nil { 550 merr := c.reporter.ReportKubernetesAPIOperationError(metrics.AssignedIdentityUpdateOperationName) 551 klog.Warningf("failed to report metrics, error: %+v", merr) 552 return 553 } 554 c.reporter.Report( 555 metrics.AssignedIdentityUpdateCountM.M(1), 556 metrics.AssignedIdentityUpdateDurationM.M(metrics.SinceInSeconds(begin))) 557 }() 558 559 v1AssignedID := aadpodv1.ConvertInternalAssignedIdentityToV1AssignedIdentity(*assignedIdentity) 560 err = c.rest.Put().Namespace(assignedIdentity.Namespace).Resource(aadpodid.AzureAssignedIDResource).Name(assignedIdentity.Name).Body(&v1AssignedID).Do(context.TODO()).Error() 561 if err != nil { 562 return fmt.Errorf("failed to update AzureAssignedIdentity, error: %+v", err) 563 } 564 565 klog.V(5).Infof("time taken to update %s/%s: %v", assignedIdentity.Namespace, assignedIdentity.Name, time.Since(begin)) 566 stats.AggregateConcurrent(stats.UpdateAzureAssignedIdentity, begin, time.Now()) 567 return nil 568 } 569 570 // ListBindings returns a list of azureidentitybindings 571 func (c *Client) ListBindings() (*[]aadpodid.AzureIdentityBinding, error) { 572 begin := time.Now() 573 574 var resList []aadpodid.AzureIdentityBinding 575 576 list := c.BindingInformer.GetStore().List() 577 for _, binding := range list { 578 o, ok := binding.(*aadpodv1.AzureIdentityBinding) 579 if !ok { 580 return nil, fmt.Errorf("failed to cast %T to %s", binding, aadpodv1.AzureIDBindingResource) 581 } 582 // Note: List items returned from cache have empty Kind and API version.. 583 // Work around this issue since we need that for event recording to work. 584 o.SetGroupVersionKind(schema.GroupVersionKind{ 585 Group: aadpodv1.SchemeGroupVersion.Group, 586 Version: aadpodv1.SchemeGroupVersion.Version, 587 Kind: reflect.TypeOf(*o).String()}) 588 589 internalBinding := aadpodv1.ConvertV1BindingToInternalBinding(*o) 590 591 resList = append(resList, internalBinding) 592 klog.V(6).Infof("appending binding: %s/%s to list.", o.Namespace, o.Name) 593 } 594 595 stats.Aggregate(stats.AzureIdentityBindingList, time.Since(begin)) 596 return &resList, nil 597 } 598 599 // ListAssignedIDs returns a list of azureassignedidentities 600 func (c *Client) ListAssignedIDs() (*[]aadpodid.AzureAssignedIdentity, error) { 601 begin := time.Now() 602 603 var resList []aadpodid.AzureAssignedIdentity 604 605 list := c.AssignedIDInformer.GetStore().List() 606 for _, assignedID := range list { 607 o, ok := assignedID.(*aadpodv1.AzureAssignedIdentity) 608 if !ok { 609 return nil, fmt.Errorf("failed to cast %T to %s", assignedID, aadpodv1.AzureAssignedIDResource) 610 } 611 // Note: List items returned from cache have empty Kind and API version.. 612 // Work around this issue since we need that for event recording to work. 613 o.SetGroupVersionKind(schema.GroupVersionKind{ 614 Group: aadpodv1.SchemeGroupVersion.Group, 615 Version: aadpodv1.SchemeGroupVersion.Version, 616 Kind: reflect.TypeOf(*o).String()}) 617 out := aadpodv1.ConvertV1AssignedIdentityToInternalAssignedIdentity(*o) 618 resList = append(resList, out) 619 klog.V(6).Infof("appending AzureAssignedIdentity: %s/%s to list.", o.Namespace, o.Name) 620 } 621 622 stats.Aggregate(stats.AzureAssignedIdentityList, time.Since(begin)) 623 return &resList, nil 624 } 625 626 // ListAssignedIDsInMap gets the list of current assigned ids, adds it to a map 627 // with assigned identity name as key and assigned identity as value. 628 func (c *Client) ListAssignedIDsInMap() (map[string]aadpodid.AzureAssignedIdentity, error) { 629 begin := time.Now() 630 631 result := make(map[string]aadpodid.AzureAssignedIdentity) 632 list := c.AssignedIDInformer.GetStore().List() 633 for _, assignedID := range list { 634 635 o, ok := assignedID.(*aadpodv1.AzureAssignedIdentity) 636 if !ok { 637 return nil, fmt.Errorf("failed to cast %T to %s", assignedID, aadpodv1.AzureAssignedIDResource) 638 } 639 // Note: List items returned from cache have empty Kind and API version.. 640 // Work around this issue since we need that for event recording to work. 641 o.SetGroupVersionKind(schema.GroupVersionKind{ 642 Group: aadpodv1.SchemeGroupVersion.Group, 643 Version: aadpodv1.SchemeGroupVersion.Version, 644 Kind: reflect.TypeOf(*o).String()}) 645 646 out := aadpodv1.ConvertV1AssignedIdentityToInternalAssignedIdentity(*o) 647 // assigned identities names are unique across namespaces as we use pod name-<id ns>-<id name> 648 result[o.Name] = out 649 klog.V(6).Infof("added to map with key: %s", o.Name) 650 } 651 652 stats.Aggregate(stats.AzureAssignedIdentityList, time.Since(begin)) 653 return result, nil 654 } 655 656 // ListIds returns a list of azureidentities 657 func (c *Client) ListIds() (*[]aadpodid.AzureIdentity, error) { 658 begin := time.Now() 659 660 var resList []aadpodid.AzureIdentity 661 662 list := c.IDInformer.GetStore().List() 663 for _, id := range list { 664 o, ok := id.(*aadpodv1.AzureIdentity) 665 if !ok { 666 return nil, fmt.Errorf("failed to cast %T to %s", id, aadpodv1.AzureIDResource) 667 } 668 // Note: List items returned from cache have empty Kind and API version.. 669 // Work around this issue since we need that for event recording to work. 670 o.SetGroupVersionKind(schema.GroupVersionKind{ 671 Group: aadpodv1.SchemeGroupVersion.Group, 672 Version: aadpodv1.SchemeGroupVersion.Version, 673 Kind: reflect.TypeOf(*o).String()}) 674 675 out := aadpodv1.ConvertV1IdentityToInternalIdentity(*o) 676 677 resList = append(resList, out) 678 klog.V(6).Infof("appending AzureIdentity %s/%s to list.", o.Namespace, o.Name) 679 } 680 681 stats.Aggregate(stats.AzureIdentityList, time.Since(begin)) 682 return &resList, nil 683 } 684 685 // ListPodIdentityExceptions returns list of azurepodidentityexceptions 686 func (c *Client) ListPodIdentityExceptions(ns string) (*[]aadpodid.AzurePodIdentityException, error) { 687 begin := time.Now() 688 689 var resList []aadpodid.AzurePodIdentityException 690 691 list := c.PodIdentityExceptionInformer.GetStore().List() 692 for _, binding := range list { 693 o, ok := binding.(*aadpodv1.AzurePodIdentityException) 694 if !ok { 695 return nil, fmt.Errorf("failed to cast %T to %s", binding, aadpodid.AzurePodIdentityExceptionResource) 696 } 697 if o.Namespace == ns { 698 // Note: List items returned from cache have empty Kind and API version.. 699 // Work around this issue since we need that for event recording to work. 700 o.SetGroupVersionKind(schema.GroupVersionKind{ 701 Group: aadpodv1.SchemeGroupVersion.Group, 702 Version: aadpodv1.SchemeGroupVersion.Version, 703 Kind: reflect.TypeOf(*o).String()}) 704 out := aadpodv1.ConvertV1PodIdentityExceptionToInternalPodIdentityException(*o) 705 706 resList = append(resList, out) 707 klog.V(6).Infof("appending exception: %s/%s to list.", o.Namespace, o.Name) 708 } 709 } 710 711 stats.Aggregate(stats.AzurePodIdentityExceptionList, time.Since(begin)) 712 return &resList, nil 713 } 714 715 // ListPodIds - given a pod with pod name space 716 // returns a map with list of azure identities in each state 717 func (c *Client) ListPodIds(podns, podname string) (map[string][]aadpodid.AzureIdentity, error) { 718 list, err := c.ListAssignedIDs() 719 if err != nil { 720 return nil, err 721 } 722 723 idStateMap := make(map[string][]aadpodid.AzureIdentity) 724 for _, v := range *list { 725 if v.Spec.Pod == podname && v.Spec.PodNamespace == podns { 726 idStateMap[v.Status.Status] = append(idStateMap[v.Status.Status], *v.Spec.AzureIdentityRef) 727 } 728 } 729 return idStateMap, nil 730 } 731 732 // GetPodIDsWithBinding returns list of azure identity based on bindings 733 // that match pod label. 734 func (c *Client) GetPodIDsWithBinding(namespace string, labels map[string]string) ([]aadpodid.AzureIdentity, error) { 735 // get all bindings 736 bindings, err := c.ListBindings() 737 if err != nil { 738 return nil, err 739 } 740 if bindings == nil { 741 return nil, fmt.Errorf("binding list is nil from cache") 742 } 743 matchingIds := make(map[string]bool) 744 podLabel := labels[aadpodid.CRDLabelKey] 745 746 for _, binding := range *bindings { 747 // check if binding selector in pod labels 748 if podLabel == binding.Spec.Selector && binding.Namespace == namespace { 749 matchingIds[binding.Spec.AzureIdentity] = true 750 } 751 } 752 // get the azure identity objects based on the list generated 753 azIdentities, err := c.ListIds() 754 if err != nil { 755 return nil, err 756 } 757 if azIdentities == nil { 758 return nil, fmt.Errorf("azure identities list is nil from cache") 759 } 760 var azIds []aadpodid.AzureIdentity 761 for _, azIdentity := range *azIdentities { 762 if _, exists := matchingIds[azIdentity.Name]; exists && azIdentity.Namespace == namespace { 763 azIds = append(azIds, azIdentity) 764 } 765 } 766 return azIds, nil 767 } 768 769 type patchStatusOps struct { 770 Op string `json:"op"` 771 Path string `json:"path"` 772 Value interface{} `json:"value"` 773 } 774 775 // UpdateAzureAssignedIdentityStatus updates the status field in AzureAssignedIdentity to indicate current status 776 func (c *Client) UpdateAzureAssignedIdentityStatus(assignedIdentity *aadpodid.AzureAssignedIdentity, status string) (err error) { 777 klog.Infof("updating AzureAssignedIdentity %s/%s status to %s", assignedIdentity.Namespace, assignedIdentity.Name, status) 778 defer func() { 779 if err != nil { 780 merr := c.reporter.ReportKubernetesAPIOperationError(metrics.UpdateAzureAssignedIdentityStatusOperationName) 781 if merr != nil { 782 klog.Warningf("failed to report metrics, error: %+v", merr) 783 } 784 } 785 }() 786 787 ops := make([]patchStatusOps, 1) 788 ops[0].Op = "replace" 789 ops[0].Path = "/status/status" 790 ops[0].Value = status 791 792 patchBytes, err := json.Marshal(ops) 793 if err != nil { 794 return err 795 } 796 797 begin := time.Now() 798 err = c.rest. 799 Patch(types.JSONPatchType). 800 Namespace(assignedIdentity.Namespace). 801 Resource(aadpodid.AzureAssignedIDResource). 802 Name(assignedIdentity.Name). 803 Body(patchBytes). 804 Do(context.TODO()). 805 Error() 806 klog.V(5).Infof("patch of %s took: %v", assignedIdentity.Name, time.Since(begin)) 807 return err 808 } 809 810 func getMapKey(ns, name string) string { 811 return strings.Join([]string{ns, name}, "/") 812 } 813 814 func removeFinalizer(assignedID *aadpodv1.AzureAssignedIdentity) { 815 assignedID.SetFinalizers(removeString(finalizerName, assignedID.GetFinalizers())) 816 } 817 818 func hasFinalizer(assignedID *aadpodv1.AzureAssignedIdentity) bool { 819 return containsString(finalizerName, assignedID.GetFinalizers()) 820 } 821 822 func containsString(s string, items []string) bool { 823 for _, item := range items { 824 if item == s { 825 return true 826 } 827 } 828 return false 829 } 830 831 func removeString(s string, items []string) []string { 832 var rval []string 833 for _, item := range items { 834 if item != s { 835 rval = append(rval, item) 836 } 837 } 838 return rval 839 }