...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/registration/registration_controller.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/registration

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package registration
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sync"
    21  	"time"
    22  
    23  	dclcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/dcl"
    24  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/deletiondefender"
    25  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/gsakeysecretgenerator"
    26  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/auditconfig"
    27  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/partialpolicy"
    28  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/policy"
    29  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/policymember"
    30  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/tf"
    31  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/unmanageddetector"
    32  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/crd/crdgeneration"
    33  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/conversion"
    34  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    35  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
    36  
    37  	"github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl"
    38  	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    39  	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    40  	"k8s.io/apimachinery/pkg/api/errors"
    41  	"k8s.io/apimachinery/pkg/runtime/schema"
    42  	"sigs.k8s.io/controller-runtime/pkg/client"
    43  	"sigs.k8s.io/controller-runtime/pkg/controller"
    44  	"sigs.k8s.io/controller-runtime/pkg/handler"
    45  	klog "sigs.k8s.io/controller-runtime/pkg/log"
    46  	"sigs.k8s.io/controller-runtime/pkg/manager"
    47  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    48  	"sigs.k8s.io/controller-runtime/pkg/source"
    49  )
    50  
    51  const controllerName = "registration-controller"
    52  const serviceAccountKeyAPIGroup = "iam.cnrm.cloud.google.com"
    53  const serviceAccountKeyKind = "IAMServiceAccountKey"
    54  
    55  var logger = klog.Log.WithName(controllerName)
    56  
    57  // Add creates a new registration Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
    58  // and Start it when the Manager is Started.
    59  func Add(mgr manager.Manager, p *tfschema.Provider, smLoader *servicemappingloader.ServiceMappingLoader, dclConfig *dcl.Config, dclConverter *conversion.Converter, regFunc registrationFunc) error {
    60  	r := &ReconcileRegistration{
    61  		Client:           mgr.GetClient(),
    62  		provider:         p,
    63  		smLoader:         smLoader,
    64  		dclConfig:        dclConfig,
    65  		dclConverter:     dclConverter,
    66  		mgr:              mgr,
    67  		controllers:      make(map[string]map[string]controllerContext),
    68  		registrationFunc: regFunc,
    69  	}
    70  	c, err := controller.New(controllerName, mgr,
    71  		controller.Options{
    72  			Reconciler:              r,
    73  			MaxConcurrentReconciles: k8s.ControllerMaxConcurrentReconciles,
    74  		})
    75  	if err != nil {
    76  		return err
    77  	}
    78  	return c.Watch(&source.Kind{Type: &apiextensions.CustomResourceDefinition{}}, &handler.EnqueueRequestForObject{}, ManagedByKCCPredicate{})
    79  }
    80  
    81  var _ reconcile.Reconciler = &ReconcileRegistration{}
    82  
    83  // ReconcileRegistration reconciles a CRD owned by KCC
    84  type ReconcileRegistration struct {
    85  	client.Client
    86  	provider         *tfschema.Provider
    87  	smLoader         *servicemappingloader.ServiceMappingLoader
    88  	dclConfig        *dcl.Config
    89  	dclConverter     *conversion.Converter
    90  	mgr              manager.Manager
    91  	controllers      map[string]map[string]controllerContext
    92  	registrationFunc registrationFunc
    93  	mu               sync.Mutex
    94  }
    95  
    96  type controllerContext struct {
    97  	registered    bool
    98  	schemaUpdater k8s.SchemaReferenceUpdater
    99  }
   100  
   101  // registrationFunc is the function that handles the registration of a controller for the given CRD and returns an interface to update its schema reference.
   102  type registrationFunc func(*ReconcileRegistration, *apiextensions.CustomResourceDefinition, schema.GroupVersionKind) (k8s.SchemaReferenceUpdater, error)
   103  
   104  func (r *ReconcileRegistration) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
   105  	// Fetch the TypeProvider tp
   106  	crd := &apiextensions.CustomResourceDefinition{}
   107  	err := r.Get(ctx, request.NamespacedName, crd)
   108  	if err != nil {
   109  		if errors.IsNotFound(err) {
   110  			// Object not found, return.  Created objects are automatically garbage collected.
   111  			// For additional cleanup logic use finalizers.
   112  			return reconcile.Result{}, nil
   113  		}
   114  		// Error reading the object - requeue the request.
   115  		return reconcile.Result{}, err
   116  	}
   117  
   118  	logger.Info("Waiting to obtain lock...", "kind", crd.Spec.Names.Kind)
   119  	start := time.Now()
   120  	r.mu.Lock()
   121  	logger.Info("Obtained lock", "kind", crd.Spec.Names.Kind, "elapsed (μs)", time.Since(start).Microseconds())
   122  	defer func() {
   123  		logger.Info("Releasing lock...", "kind", crd.Spec.Names.Kind)
   124  		r.mu.Unlock()
   125  	}()
   126  	gvk := schema.GroupVersionKind{
   127  		Group:   crd.Spec.Group,
   128  		Version: k8s.GetVersionFromCRD(crd),
   129  		Kind:    crd.Spec.Names.Kind,
   130  	}
   131  	if kindMapForGroup, exists := r.controllers[gvk.Group]; exists {
   132  		if kindMapForGroup[gvk.Kind].registered {
   133  			logger.Info("controller already registered for kind in API group", "group", gvk.Group, "version", gvk.Version, "kind", gvk.Kind)
   134  			if kindMapForGroup[gvk.Kind].schemaUpdater != nil {
   135  				logger.Info("updating schema for controller", "group", gvk.Group, "version", gvk.Version, "kind", gvk.Kind)
   136  				if err := kindMapForGroup[gvk.Kind].schemaUpdater.UpdateSchema(crd); err != nil {
   137  					logger.Info("error updating schema for controller", "group", gvk.Group, "version", gvk.Version, "kind", gvk.Kind)
   138  				}
   139  			}
   140  			return reconcile.Result{}, nil
   141  		}
   142  	} else {
   143  		r.controllers[gvk.Group] = make(map[string]controllerContext)
   144  	}
   145  
   146  	schemaUpdater, err := r.registrationFunc(r, crd, gvk)
   147  	if err != nil {
   148  		return reconcile.Result{}, fmt.Errorf("error registering controller: %w", err)
   149  	}
   150  
   151  	r.controllers[gvk.Group][gvk.Kind] = controllerContext{registered: true, schemaUpdater: schemaUpdater}
   152  	return reconcile.Result{}, nil
   153  }
   154  
   155  func isServiceAccountKeyCRD(crd *apiextensions.CustomResourceDefinition) bool {
   156  	return crd.Spec.Group == serviceAccountKeyAPIGroup && crd.Spec.Names.Kind == serviceAccountKeyKind
   157  }
   158  
   159  func RegisterDefaultController(r *ReconcileRegistration, crd *apiextensions.CustomResourceDefinition, gvk schema.GroupVersionKind) (k8s.SchemaReferenceUpdater, error) {
   160  	if _, ok := k8s.IgnoredKindList[crd.Spec.Names.Kind]; ok {
   161  		return nil, nil
   162  	}
   163  	// Depending on which resource it is, we need to register a different controller.
   164  	var schemaUpdater k8s.SchemaReferenceUpdater
   165  	switch gvk.Kind {
   166  	case "IAMPolicy":
   167  		if err := policy.Add(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig); err != nil {
   168  			return nil, err
   169  		}
   170  	case "IAMPartialPolicy":
   171  		if err := partialpolicy.Add(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig); err != nil {
   172  			return nil, err
   173  		}
   174  	case "IAMPolicyMember":
   175  		if err := policymember.Add(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig); err != nil {
   176  			return nil, err
   177  		}
   178  	case "IAMAuditConfig":
   179  		if err := auditconfig.Add(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig); err != nil {
   180  			return nil, err
   181  		}
   182  	default:
   183  		// register controllers for dcl-based CRDs
   184  		if val, ok := crd.Labels[k8s.DCL2CRDLabel]; ok && val == "true" {
   185  			su, err := dclcontroller.Add(r.mgr, crd, r.dclConverter, r.dclConfig, r.smLoader)
   186  			if err != nil {
   187  				return nil, fmt.Errorf("error adding dcl controller for %v to a manager: %v", crd.Spec.Names.Kind, err)
   188  			}
   189  			return su, nil
   190  		}
   191  		// register controllers for tf-based CRDs
   192  		if val, ok := crd.Labels[crdgeneration.TF2CRDLabel]; !ok || val != "true" {
   193  			logger.Info("unrecognized CRD; skipping controller registration", "group", gvk.Group, "version", gvk.Version, "kind", gvk.Kind)
   194  			return nil, nil
   195  		}
   196  		su, err := tf.Add(r.mgr, crd, r.provider, r.smLoader)
   197  		if err != nil {
   198  			return nil, fmt.Errorf("error adding terraform controller for %v to a manager: %v", crd.Spec.Names.Kind, err)
   199  		}
   200  		schemaUpdater = su
   201  		// register the controller to automatically create secrets for GSA keys
   202  		if isServiceAccountKeyCRD(crd) {
   203  			logger.Info("registering the GSA-Key-to-Secret generation controller")
   204  			if err := gsakeysecretgenerator.Add(r.mgr, crd); err != nil {
   205  				return nil, fmt.Errorf("error adding the gsa-to-secret generator for %v to a manager: %v", crd.Spec.Names.Kind, err)
   206  			}
   207  		}
   208  	}
   209  	return schemaUpdater, nil
   210  }
   211  
   212  func RegisterDeletionDefenderController(r *ReconcileRegistration, crd *apiextensions.CustomResourceDefinition, _ schema.GroupVersionKind) (k8s.SchemaReferenceUpdater, error) {
   213  	if _, ok := k8s.IgnoredKindList[crd.Spec.Names.Kind]; ok {
   214  		return nil, nil
   215  	}
   216  	if err := deletiondefender.Add(r.mgr, crd); err != nil {
   217  		return nil, fmt.Errorf("error registering deletion defender controller for '%v': %w", crd.GetName(), err)
   218  	}
   219  	return nil, nil
   220  }
   221  
   222  func RegisterUnmanagedDetectorController(r *ReconcileRegistration, crd *apiextensions.CustomResourceDefinition, _ schema.GroupVersionKind) (k8s.SchemaReferenceUpdater, error) {
   223  	if _, ok := k8s.IgnoredKindList[crd.Spec.Names.Kind]; ok {
   224  		return nil, nil
   225  	}
   226  	if err := unmanageddetector.Add(r.mgr, crd); err != nil {
   227  		return nil, fmt.Errorf("error registering unmanaged detector controller for '%v': %w", crd.GetName(), err)
   228  	}
   229  	return nil, nil
   230  }
   231  

View as plain text