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  }