diff --git a/README.md b/README.md index 87e1c069c..d1fb40287 100644 --- a/README.md +++ b/README.md @@ -149,3 +149,12 @@ In order to create an instance of gatekeeper in the specified namespace you can ```shell kubectl create -f config/samples/operator_v1alpha1_gatekeeper.yaml ``` + +## Updating gatekeeper + +### Update gatekeeper package according to gatekeeper version + +The gatekeeper package version should be updated with matched gatekeeper version when gatekeeper version updated in go.mod. +``` +require github.com/open-policy-agent/gatekeeper/v3 v3.13.4 +``` diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index eded79c11..bf671656c 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -33,10 +33,4 @@ var ( // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme - - GatekeeperGVR = schema.GroupVersionResource{ - Group: GroupVersion.Group, - Version: GroupVersion.Version, - Resource: "gatekeeper", - } ) diff --git a/controllers/constraintstatus_controller.go b/controllers/constraintstatus_controller.go index c6625e508..468715163 100644 --- a/controllers/constraintstatus_controller.go +++ b/controllers/constraintstatus_controller.go @@ -3,16 +3,18 @@ package controllers import ( "context" "strings" + "sync" "time" + operatorv1alpha1 "github.com/gatekeeper/gatekeeper-operator/api/v1alpha1" "github.com/go-logr/logr" + constraintV1 "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1beta1" "github.com/open-policy-agent/gatekeeper/v3/apis/config/v1alpha1" "github.com/open-policy-agent/gatekeeper/v3/apis/status/v1beta1" "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" @@ -27,7 +29,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -var ControllerName = "contraintstatus_reconciler" +var ( + ControllerName = "constraintstatus_reconciler" + ErrNotFoundDiscovery = errors.New("There is no matched apiGroup, version or kind") +) type discoveryInfo struct { apiResourceList []*metav1.APIResourceList @@ -36,12 +41,11 @@ type discoveryInfo struct { type ConstraintStatusReconciler struct { client.Client - Log logr.Logger - DynamicClient dynamic.Interface - ClientSet *kubernetes.Clientset - Scheme *runtime.Scheme - Namespace string - MaxConcurrentReconciles uint + Log logr.Logger + DynamicClient dynamic.Interface + ClientSet *kubernetes.Clientset + Namespace string + constraintMap sync.Map discoveryInfo } @@ -50,7 +54,7 @@ func (r *ConstraintStatusReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). // The work queue prevents the same item being reconciled concurrently: // https://github.com/kubernetes-sigs/controller-runtime/issues/1416#issuecomment-899833144 - WithOptions(controller.Options{MaxConcurrentReconciles: int(r.MaxConcurrentReconciles)}). + WithOptions(controller.Options{MaxConcurrentReconciles: int(5)}). // watch Config resrouce as secondary Named(ControllerName). For(&v1beta1.ConstraintPodStatus{}, @@ -80,13 +84,17 @@ func (r *ConstraintStatusReconciler) SetupWithManager(mgr ctrl.Manager) error { } // User set gatekeeper.spec.audit.auditFromCache to Automatic, this reconcile function -// collect requested constraints and add found kinds which is used in contraint to config.spec.sync.syncOnly +// collect requested constraints and add found kinds which is used in constraint to config.spec.sync.syncOnly func (r *ConstraintStatusReconciler) Reconcile(ctx context.Context, request reconcile.Request, ) (reconcile.Result, error) { log := r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) log.Info("Reconciling ConstraintPodStatus and Config") + // check Automatic set in gatekeeper resource + if !r.getAutomaticSet(ctx) { + return reconcile.Result{RequeueAfter: time.Minute * 3}, nil + } // Get config or create if not exist config := &v1alpha1.Config{} err := r.Get(ctx, types.NamespacedName{ @@ -104,16 +112,23 @@ func (r *ConstraintStatusReconciler) Reconcile(ctx context.Context, err = r.Create(ctx, config) if err != nil { + log.Error(err, "Fail to Create Config resource, requeue") + return reconcile.Result{}, err } } - contraintPodStatus := &v1beta1.ConstraintPodStatus{} - err = r.Get(ctx, request.NamespacedName, contraintPodStatus) + constraintPodStatus := &v1beta1.ConstraintPodStatus{} + err = r.Get(ctx, request.NamespacedName, constraintPodStatus) if err != nil { if apierrors.IsNotFound(err) { - log.Error(err, "Cannot find any contraintStatus") + log.V(1).Info("Cannot find any constraintStatus") + + err = r.handleDeleteEvent(ctx, config) + if err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, nil } @@ -121,17 +136,17 @@ func (r *ConstraintStatusReconciler) Reconcile(ctx context.Context, return reconcile.Result{}, err } - labels := contraintPodStatus.GetLabels() + labels := constraintPodStatus.GetLabels() constraintKind := labels["internal.gatekeeper.sh/constraint-kind"] constraintName := labels["internal.gatekeeper.sh/constraint-name"] - table := map[v1alpha1.SyncOnlyEntry]bool{} - // Add to table for uniqueue filtering + syncOnlySet := map[v1alpha1.SyncOnlyEntry]bool{} + // Add to table for unique filtering for _, entry := range config.Spec.Sync.SyncOnly { - table[entry] = true + syncOnlySet[entry] = true } - + // start constraintGVR := schema.GroupVersionResource{ Group: "constraints.gatekeeper.sh", Version: "v1beta1", @@ -140,32 +155,36 @@ func (r *ConstraintStatusReconciler) Reconcile(ctx context.Context, constraint, err := r.DynamicClient.Resource(constraintGVR).Get(ctx, constraintName, metav1.GetOptions{}) if err != nil { - return reconcile.Result{}, err - } + if apierrors.IsNotFound(err) { + r.Log.Info("The Constraint is deleted in the config resouce", constraint.GetName()) + + return reconcile.Result{}, nil + } - constraintMatchKinds, _, err := unstructured.NestedSlice(constraint.Object, "spec", "match", "kinds") - if err != nil { return reconcile.Result{}, err } - contraintSyncOnlyEntries, err := r.getSyncOnlys(constraintMatchKinds) + constraintMatchKinds, _, err := unstructured.NestedSlice(constraint.Object, "spec", "match", "kinds") if err != nil { - log.Error(err, "Error to get matching kind and apigroup") + r.Log.V(1).Info("There is no provided kinds in contsraint", constraint.GetName()) return reconcile.Result{}, nil } + // remove + constraintSyncOnlyEntries, err := r.getSyncOnlys(constraintMatchKinds) + if err != nil { + if errors.Is(err, ErrNotFoundDiscovery) { + return reconcile.Result{RequeueAfter: time.Minute * 3}, nil + } - for _, ce := range *contraintSyncOnlyEntries { - table[ce] = true - } + log.Error(err, "Error to get matching kind and apigroup") - syncOnlys := []v1alpha1.SyncOnlyEntry{} - for key := range table { - syncOnlys = append(syncOnlys, key) + return reconcile.Result{}, nil } - config.Spec.Sync.SyncOnly = syncOnlys - err = r.Update(ctx, config, &client.UpdateOptions{}) + r.addConstraintToMap(constraint.GetName(), *constraintSyncOnlyEntries) + updatedSyncOnly := r.getUniqSyncOnly() + err = r.updateConfigSyncOnlyEntry(ctx, config, updatedSyncOnly) if err != nil { return reconcile.Result{}, err @@ -179,14 +198,25 @@ func (r *ConstraintStatusReconciler) getSyncOnlys(constraintMatchKinds []interfa ) { syncOnlys := []v1alpha1.SyncOnlyEntry{} - for _, kind := range constraintMatchKinds { - newKind := kind.(map[string]interface{}) - apiGroups := newKind["apiGroups"].([]interface{}) - kindsInKinds := newKind["kinds"].([]interface{}) + for _, match := range constraintMatchKinds { + newKind, ok := match.(map[string]interface{}) + if !ok { + continue + } + + apiGroups, ok := newKind["apiGroups"].([]interface{}) + if !ok { + continue + } + + kindsInKinds, ok := newKind["kinds"].([]interface{}) + if !ok { + continue + } for _, apiGroup := range apiGroups { - for _, kindkind := range kindsInKinds { - version, err := r.getAPIVersion(kindkind.(string), apiGroup.(string)) + for _, kind := range kindsInKinds { + version, err := r.getAPIVersion(kind.(string), apiGroup.(string), false) if err != nil { return nil, err } @@ -194,7 +224,7 @@ func (r *ConstraintStatusReconciler) getSyncOnlys(constraintMatchKinds []interfa syncOnlys = append(syncOnlys, v1alpha1.SyncOnlyEntry{ Group: apiGroup.(string), Version: version, - Kind: kindkind.(string), + Kind: kind.(string), }) } } @@ -203,7 +233,7 @@ func (r *ConstraintStatusReconciler) getSyncOnlys(constraintMatchKinds []interfa return &syncOnlys, nil } -func (r *ConstraintStatusReconciler) getAPIVersion(kind string, apiGroup string) (string, error) { +func (r *ConstraintStatusReconciler) getAPIVersion(kind string, apiGroup string, skipRefresh bool) (string, error) { // cool time(10 min) to refresh discoveries if len(r.apiResourceList) == 0 || r.discoveryLastRefreshed.Add(time.Minute*10).Before(time.Now()) { @@ -218,9 +248,9 @@ func (r *ConstraintStatusReconciler) getAPIVersion(kind string, apiGroup string) for _, resc := range r.apiResourceList { groupVerison, err := schema.ParseGroupVersion(resc.GroupVersion) if err != nil { - r.Log.Error(err, "Cannot Parse Group and version in getApiVersion") + r.Log.Error(err, "Cannot Parse Group and version in getApiVersion ", "GroupVersion:", resc.GroupVersion) - return "", errors.New("Error in parse Group and version in getApiVersion function") + continue } group := groupVerison.Group @@ -233,15 +263,19 @@ func (r *ConstraintStatusReconciler) getAPIVersion(kind string, apiGroup string) } } - // Get new discoveryInfo, when any resource is not found - err := r.refreshDiscoveryInfo() - r.discoveryLastRefreshed = time.Now() + if !skipRefresh { + // Get new discoveryInfo, when any resource is not found + err := r.refreshDiscoveryInfo() + if err != nil { + return "", err + } - if err != nil { - return "", err + r.discoveryLastRefreshed = time.Now() + // Retry one more time after refresh the discovery + return r.getAPIVersion(kind, apiGroup, true) } - return "", errors.New("Getting discovery has error") + return "", ErrNotFoundDiscovery } // Retrieve all groups and versions to add in config sync @@ -258,3 +292,171 @@ func (r *ConstraintStatusReconciler) refreshDiscoveryInfo() error { return nil } + +// Check audit.automatic set in gatekeeper resource. +// audit.automatic is false or not set then skip adding constraintpodstatus controller +func (r *ConstraintStatusReconciler) getAutomaticSet(ctx context.Context) bool { + gatekeeper := &operatorv1alpha1.Gatekeeper{} + + if err := r.Get(ctx, types.NamespacedName{ + Namespace: "", + Name: "gatekeeper", + }, gatekeeper); err != nil { + if apierrors.IsNotFound(err) { + r.Log.V(1).Info("Gatekeeper resource is not found") + + return false + } + + r.Log.Error(err, "Getting gatekeeper resource has error") + + return false + } + + audit := gatekeeper.Spec.Audit + if audit == nil { + return false + } + + auditFromCache := audit.AuditFromCache + if auditFromCache == nil { + return false + } + + if *auditFromCache != operatorv1alpha1.AuditFromCacheAutomatic { + r.Log.V(1).Info("AuditFromCache is not set") + + return false + } + + return true +} + +func (r *ConstraintStatusReconciler) getUniqSyncOnly() []v1alpha1.SyncOnlyEntry { + syncOnlySet := map[v1alpha1.SyncOnlyEntry]bool{} + // Add to table for unique filtering + r.constraintMap.Range(func(ckey interface{}, entries interface{}) bool { + constraintName, ok := ckey.(string) + if !ok { + r.Log.V(1).Info("Error to parse constraint name") + + return true + } + + arrayEntries, ok := entries.([]v1alpha1.SyncOnlyEntry) + if !ok { + r.Log.V(1).Info("Error to parse Constraint SyncOnlyEntry values", "constraintName:", constraintName) + + return true + } + + for _, entry := range arrayEntries { + syncOnlySet[entry] = true + } + + return true + }) + + syncOnlys := []v1alpha1.SyncOnlyEntry{} + for key := range syncOnlySet { + syncOnlys = append(syncOnlys, key) + } + + return syncOnlys +} + +func (r *ConstraintStatusReconciler) updateConfigSyncOnlyEntry( + ctx context.Context, config *v1alpha1.Config, syncOnlys []v1alpha1.SyncOnlyEntry, +) error { + config.Spec.Sync.SyncOnly = syncOnlys + + return r.Update(ctx, config, &client.UpdateOptions{}) +} + +func (r *ConstraintStatusReconciler) addConstraintToMap(ckey string, entries []v1alpha1.SyncOnlyEntry) { + r.constraintMap.Store(ckey, entries) +} + +// This function works when constraintPodStatus deleted event. +// Collect all ConstraintTemplates and refresh r.constraintMap with current constraint +func (r *ConstraintStatusReconciler) handleDeleteEvent(ctx context.Context, config *v1alpha1.Config) error { + templateList := &constraintV1.ConstraintTemplateList{} + + if err := r.Client.List(ctx, templateList, &client.ListOptions{}); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + + // There is no ConstraintTemplate reset constraintMap + r.resetConstraintMap() + } + + templateCrdKinds := map[string]bool{} + for _, temp := range templateList.Items { + templateCrdKinds[temp.Spec.CRD.Spec.Names.Kind] = true + } + + for crdKind := range templateCrdKinds { + constraintGVR := schema.GroupVersionResource{ + Group: "constraints.gatekeeper.sh", + Version: "v1beta1", + Resource: strings.ToLower(crdKind), + } + + constraintList, err := r.DynamicClient.Resource(constraintGVR).List(ctx, metav1.ListOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil + } + + return err + } + + // reset constraintMap + r.resetConstraintMap() + + // Add discovered constraint into constraintMap + for _, constraint := range constraintList.Items { + constraintMatchKinds, _, err := unstructured.NestedSlice(constraint.Object, "spec", "match", "kinds") + if err != nil { + r.Log.V(1).Info("There is no provided kinds in this contsraint", + "constraintName:", constraint.GetName()) + + continue + } + + constraintSyncOnlyEntries, err := r.getSyncOnlys(constraintMatchKinds) + if err != nil { + if errors.Is(err, ErrNotFoundDiscovery) { + r.Log.V(1).Info("There is no provided kinds in this contsraint", + "constraintName:", constraint.GetName()) + + continue + } + + r.Log.Error(err, "Error to get matching kind and apigroup") + + return err + } + + r.addConstraintToMap(constraint.GetName(), *constraintSyncOnlyEntries) + } + } + + updatedSyncOnly := r.getUniqSyncOnly() + + err := r.updateConfigSyncOnlyEntry(ctx, config, updatedSyncOnly) + if err != nil { + return err + } + + return nil +} + +func (r *ConstraintStatusReconciler) resetConstraintMap() { + r.constraintMap.Range(func(ckey interface{}, _ interface{}) bool { + r.constraintMap.Delete(ckey) + + return true + }) +} diff --git a/go.mod b/go.mod index 10a2c4853..c40c7c724 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,8 @@ require ( github.com/go-logr/logr v1.2.4 github.com/onsi/ginkgo/v2 v2.13.0 github.com/onsi/gomega v1.29.0 - github.com/open-policy-agent/gatekeeper/v3 v3.13.3 + github.com/open-policy-agent/frameworks/constraint v0.0.0-20231030230613-2e0cb3d68575 + github.com/open-policy-agent/gatekeeper/v3 v3.13.4 github.com/pkg/errors v0.9.1 golang.org/x/exp v0.0.0-20231006140011-7918f672742d k8s.io/api v0.28.3 @@ -22,6 +23,7 @@ require ( require ( github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/aws/aws-sdk-go v1.44.23 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -51,7 +53,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/open-policy-agent/frameworks/constraint v0.0.0-20230712214810-96753a21c26f // indirect github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect @@ -70,8 +71,8 @@ require ( golang.org/x/tools v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 43bed4ede..8bf6b3669 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/monitoring v1.13.0 h1:2qsrgXGVoRXpP7otZ14eE1I568zAa92sJSDPyOJvwjM= -cloud.google.com/go/trace v1.9.0 h1:olxC0QHC59zgJVALtgqfD9tGk0lfeCP5/AGXL3Px/no= +cloud.google.com/go/monitoring v1.15.1 h1:65JhLMd+JiYnXr6j5Z63dUYCuOg770p8a/VC+gil/58= +cloud.google.com/go/trace v1.10.1 h1:EwGdOLCNfYOOPtgqo+D2sDLZmRCEO1AagRTJCU6ztdg= contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= @@ -10,7 +10,8 @@ github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0= +github.com/aws/aws-sdk-go v1.44.23 h1:oFvpKJk5qdprnCcuCWk2/CADdvfYtyduQ392bMXjlYI= +github.com/aws/aws-sdk-go v1.44.23/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -32,7 +33,6 @@ github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= @@ -75,15 +75,18 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 h1:lLT7ZLSzGLI08vc9cpd+tYmNWjdKDqyr/2L+f6U12Fk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -112,11 +115,11 @@ github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4 github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/open-policy-agent/frameworks/constraint v0.0.0-20230712214810-96753a21c26f h1:dJDnp6A6LBrU/hbve5NzZNV3OzPYXdD0SJUn+xAPj+I= -github.com/open-policy-agent/frameworks/constraint v0.0.0-20230712214810-96753a21c26f/go.mod h1:54/KzLMvA5ndBVpm7B1OjLeV0cUtTLTz2bZ2OtydLpU= -github.com/open-policy-agent/gatekeeper/v3 v3.13.3 h1:f+fjtY3YagOBpGx171MlvZBgaoM8oCJRlPy9QouK3eY= -github.com/open-policy-agent/gatekeeper/v3 v3.13.3/go.mod h1:X6mSXB2vc1zz7WiyM6+vQLGyrM8ry10D21o/Ftyr0xw= -github.com/open-policy-agent/opa v0.54.0 h1:mGEsK+R5ZTMV8fzzbNzmYDGbTmY30wmRCIHmtm2VqWs= +github.com/open-policy-agent/frameworks/constraint v0.0.0-20231030230613-2e0cb3d68575 h1:rhln22JjTgsJGL8gDK4qEM372Ei1PPQk4ZTIOKM9WvY= +github.com/open-policy-agent/frameworks/constraint v0.0.0-20231030230613-2e0cb3d68575/go.mod h1:AaCd/gbQ31R7btHO450Kdp18/Zmvn7hjEt7Qbp+MfJM= +github.com/open-policy-agent/gatekeeper/v3 v3.13.4 h1:WkrjM/15O8AxC/a3wYXImQX1UcpXCAnFYI8B07FEMso= +github.com/open-policy-agent/gatekeeper/v3 v3.13.4/go.mod h1:fFv+yG/u8UGFdShqRB2LInw+yBdZ2OXPFsmFlGIdq5I= +github.com/open-policy-agent/opa v0.57.1 h1:LAa4Z0UkpjV94nRLy6XCvgOacQ6N1jf8TJLMUIzFRqc= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -156,15 +159,14 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.37.0 h1:yt2NKzK7Vyo6h0+X8BA4FpreZQTlVEIarnsBP/H5mzs= -go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc= -go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8= -go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= -go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= @@ -174,6 +176,7 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= @@ -225,15 +228,15 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= diff --git a/main.go b/main.go index 842a7e106..a5dfbcd0a 100644 --- a/main.go +++ b/main.go @@ -36,6 +36,7 @@ import ( "github.com/gatekeeper/gatekeeper-operator/pkg/platform" "github.com/gatekeeper/gatekeeper-operator/pkg/util" "github.com/gatekeeper/gatekeeper-operator/pkg/version" + constraintV1 "github.com/open-policy-agent/frameworks/constraint/pkg/apis/templates/v1beta1" "github.com/open-policy-agent/gatekeeper/v3/apis/config/v1alpha1" "github.com/open-policy-agent/gatekeeper/v3/apis/status/v1beta1" "github.com/pkg/errors" @@ -44,7 +45,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" @@ -74,13 +74,13 @@ func init() { utilruntime.Must(v1beta1.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) utilruntime.Must(v1.AddToScheme(scheme)) + utilruntime.Must(constraintV1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } -type ContraintStatusInstaller struct { +type ConstraintStatusInstaller struct { mu sync.Mutex isInstalled bool - client client.Client } func main() { @@ -155,32 +155,27 @@ func main() { dynamicClient := dynamic.NewForConfigOrDie(mgr.GetConfig()) wg.Add(1) - c := ContraintStatusInstaller{isInstalled: false, client: mgr.GetClient()} + c := ConstraintStatusInstaller{isInstalled: false} go c.watchGatekeeperInstall(mainCtx, dynamicClient, cfg, ctrl.Options{ Scheme: scheme, Metrics: server.Options{ BindAddress: ":8083", }, - WebhookServer: webhook.NewServer( - webhook.Options{ - Port: 9444, - }, - ), HealthProbeBindAddress: ":8084", LeaderElection: enableLeaderElection, - LeaderElectionID: "5ff985ccc.gatekeeper.sh", + LeaderElectionID: "5ff985ccc.constraintstatuspod.gatekeeper.sh", Cache: cacheRuntime.Options{ ByObject: map[client.Object]cacheRuntime.ByObject{ &v1beta1.ConstraintPodStatus{}: { Transform: func(obj interface{}) (interface{}, error) { - contraintStatus := obj.(*v1beta1.ConstraintPodStatus) + constraintStatus := obj.(*v1beta1.ConstraintPodStatus) // Only cache fields that are utilized by the controllers. guttedObj := &v1beta1.ConstraintPodStatus{ - TypeMeta: contraintStatus.TypeMeta, - ObjectMeta: contraintStatus.ObjectMeta, + TypeMeta: constraintStatus.TypeMeta, + ObjectMeta: constraintStatus.ObjectMeta, Status: v1beta1.ConstraintPodStatusStatus{ - Operations: contraintStatus.Status.Operations, + Operations: constraintStatus.Status.Operations, }, } return guttedObj, nil @@ -204,8 +199,6 @@ func main() { if err := mgr.Start(mainCtx); err != nil { setupLog.Error(err, "problem running manager") - // On errors, the parent context (mainCtx) may not have closed, so cancel the child context. - os.Exit(1) } wg.Wait() @@ -228,7 +221,7 @@ func gatekeeperNamespace(platformInfo platform.PlatformInfo) (string, error) { return ns, nil } -func (c *ContraintStatusInstaller) addConstraintStatusManager(ctx context.Context, mgr manager.Manager, +func (c *ConstraintStatusInstaller) addConstraintStatusManager(ctx context.Context, mgr manager.Manager, dynamicClient *dynamic.DynamicClient, namespace string, ) error { if err := (&controllers.ConstraintStatusReconciler{ @@ -236,7 +229,6 @@ func (c *ContraintStatusInstaller) addConstraintStatusManager(ctx context.Contex ClientSet: kubernetes.NewForConfigOrDie(mgr.GetConfig()), DynamicClient: dynamicClient, Log: ctrl.Log.WithName("controllers").WithName("ConstraintPodStatus"), - Scheme: mgr.GetScheme(), Namespace: namespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ConstraintPodStatus") @@ -259,57 +251,22 @@ func (c *ContraintStatusInstaller) addConstraintStatusManager(ctx context.Contex return nil } -func (c *ContraintStatusInstaller) getIsInstalled() bool { +func (c *ConstraintStatusInstaller) getIsInstalled() bool { c.mu.Lock() defer c.mu.Unlock() return c.isInstalled } -func (c *ContraintStatusInstaller) setIsInstalled(isInstalled bool) { +func (c *ConstraintStatusInstaller) setIsInstalled(isInstalled bool) { c.mu.Lock() c.isInstalled = isInstalled c.mu.Unlock() } -// Check audit.automatic set in gatekeeper resource. -// audit.automatic is false or not set then skip adding contraintpodstatus controller -func (c *ContraintStatusInstaller) getAutomaticSet(mainCtx context.Context) bool { - gatekeeper := &operatorv1alpha1.Gatekeeper{} - err := c.client.Get(mainCtx, types.NamespacedName{ - Namespace: "", - Name: "gatekeeper", - }, gatekeeper) - if err != nil { - setupLog.Error(err, "Getting gatekeeper resource has error") - - return false - } - - audit := gatekeeper.Spec.Audit - if audit == nil { - return false - } - - auditFromCache := audit.AuditFromCache - if auditFromCache == nil { - return false - } - - auditFromCacheValue := *auditFromCache - if err != nil || auditFromCacheValue != operatorv1alpha1.AuditFromCacheAutomatic { - setupLog.V(1).Info("AuditFromCache is not set") - - return false - } - - return true -} - // Check constraintpodstatuses.status.gatekeeper.sh crd status has "NamesAccepted" condition. -func (c *ContraintStatusInstaller) isApiAvailable(result watch.Event) bool { - obj := result.Object.DeepCopyObject() - crd, ok := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) +func (c *ConstraintStatusInstaller) isApiAvailable(result watch.Event) bool { + crd, ok := runtime.DefaultUnstructuredConverter.ToUnstructured(result.Object) if ok != nil { return false } @@ -322,11 +279,12 @@ func (c *ContraintStatusInstaller) isApiAvailable(result watch.Event) bool { for _, condition := range conditions { conditionObj, ok := condition.(map[string]interface{}) if !ok { - return false + continue } + conditionStatus := conditionObj["status"].(string) conditionType := conditionObj["type"].(string) - if conditionType == string(v1.NamesAccepted) { + if conditionType == string(v1.NamesAccepted) && conditionStatus == "True" { return true } } @@ -339,67 +297,77 @@ func (c *ContraintStatusInstaller) isApiAvailable(result watch.Event) bool { // Watch the constraintpodstatuses resource in this function. When adding or Modified events comes, // This function adds constraintpodstatus function. Modified events are expected as status changes // When deleting event comes, close the function using context -func (c *ContraintStatusInstaller) watchGatekeeperInstall(mainCtx context.Context, dynamicClient *dynamic.DynamicClient, +func (c *ConstraintStatusInstaller) watchGatekeeperInstall(mainCtx context.Context, dynamicClient *dynamic.DynamicClient, cfg *rest.Config, managerOptions manager.Options, namespace string, ) { - contraintStatusCtx := context.Background() - ctx, ctxCancel := context.WithCancel(contraintStatusCtx) - - fieldSelector := "metadata.name=constraintpodstatuses.status.gatekeeper.sh" - timeout := int64(30) - crdGVR := schema.GroupVersionResource{ - Group: "apiextensions.k8s.io", - Version: "v1", - Resource: "customresourcedefinitions", - } - - watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { - return dynamicClient.Resource(crdGVR).Watch(mainCtx, - metav1.ListOptions{FieldSelector: fieldSelector, TimeoutSeconds: &timeout}) - } - - watcher, _ := toolsWatch.NewRetryWatcher(crdGVR.Version, &cache.ListWatch{WatchFunc: watchFunc}) + var ctx context.Context + var ctxCancel context.CancelFunc + watcher, _ := c.newWatcher(mainCtx, dynamicClient) for { select { - case result := <-watcher.ResultChan(): + case result, ok := <-watcher.ResultChan(): + // the channel got closed, so we need to restart + if !ok { + // main context closed. This loop should be closed. + if mainCtx.Err() != nil { + ctxCancel() + return + } + watcher, _ = c.newWatcher(mainCtx, dynamicClient) + continue + } switch result.Type { //nolint:exhaustive case watch.Modified, watch.Added: - if !c.getIsInstalled() { - isAutomaticSet := c.getAutomaticSet(mainCtx) - if !isAutomaticSet { - break + if c.getIsInstalled() { + continue + } + + // Check constraintpodstatuses crd available + if !c.isApiAvailable(result) { + break + } + + ctx, ctxCancel = context.WithCancel(mainCtx) + c.setIsInstalled(true) + + go func() { + mgr, err := ctrl.NewManager(cfg, managerOptions) + if err != nil { + setupLog.Error(err, "unable to setup ConstraintStatus manager") } - // Check constraintpodstatuses crd available - if !c.isApiAvailable(result) { - break + // Start with new context since previous context was cancelled + err = c.addConstraintStatusManager(ctx, mgr, dynamicClient, namespace) + if err != nil { + setupLog.Error(err, "unable to start ConstraintStatus manager") } - c.mu.Lock() - contraintStatusCtx = context.Background() - ctx, ctxCancel = context.WithCancel(contraintStatusCtx) - c.mu.Unlock() - - c.setIsInstalled(true) - go func() { - mgr, err := ctrl.NewManager(cfg, managerOptions) - if err != nil { - setupLog.Error(err, "unable to setup ConstraintStatus manager") - } - // Start with new context since previous context was cancelled - err = c.addConstraintStatusManager(ctx, mgr, dynamicClient, namespace) - if err != nil { - setupLog.Error(err, "unable to start ConstraintStatus manager") - } - }() - } + }() case watch.Deleted: c.setIsInstalled(false) ctxCancel() setupLog.Info("Gatekeeper deleted") } case <-mainCtx.Done(): + // When parents ctx close, then child should be closed ctxCancel() return } } } + +func (c *ConstraintStatusInstaller) newWatcher(mainCtx context.Context, dynamicClient *dynamic.DynamicClient) (*toolsWatch.RetryWatcher, error) { + fieldSelector := "metadata.name=constraintpodstatuses.status.gatekeeper.sh" + timeout := int64(30) + crdGVR := schema.GroupVersionResource{ + Group: "apiextensions.k8s.io", + Version: "v1", + Resource: "customresourcedefinitions", + } + + watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { + return dynamicClient.Resource(crdGVR).Watch(mainCtx, + metav1.ListOptions{FieldSelector: fieldSelector, TimeoutSeconds: &timeout}) + } + + return toolsWatch.NewRetryWatcher(crdGVR.Version, &cache.ListWatch{WatchFunc: watchFunc}) +} diff --git a/test/e2e/case1_audit_from_cache_test.go b/test/e2e/case1_audit_from_cache_test.go index 91143c505..67f9e1b22 100644 --- a/test/e2e/case1_audit_from_cache_test.go +++ b/test/e2e/case1_audit_from_cache_test.go @@ -111,7 +111,7 @@ var _ = Describe("Test auditFromCache", Label("config"), Ordered, func() { err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) return err - }, timeout*2).ShouldNot(HaveOccurred()) + }, timeout).ShouldNot(HaveOccurred()) Eventually(func(g Gomega) []v1alpha1.SyncOnlyEntry { err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) @@ -152,12 +152,87 @@ var _ = Describe("Test auditFromCache", Label("config"), Ordered, func() { Should(ContainSubstring("cached data: {\"case1-pod\": {\"apiVersion\": \"v1\", \"kind\": \"Pod\"")) }) }) + Describe("Gatekeeper with auditFromCache=Automatic delete syncOnly config", Ordered, func() { + It("Should have 3 syncOnly elements in config", func() { + config := &v1alpha1.Config{} + By("config syncOnly should have 3 elements") + + Eventually(func(g Gomega) []v1alpha1.SyncOnlyEntry { + err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) + g.Expect(err).ShouldNot(HaveOccurred()) + + return config.Spec.Sync.SyncOnly + }, timeout).Should(HaveLen(3)) + }) + It("Should have 2 syncOnly elements in config", func() { + Kubectl("delete", "-f", case1ConstraintIngressYaml, "--ignore-not-found") + + config := &v1alpha1.Config{} + + Eventually(func(g Gomega) []v1alpha1.SyncOnlyEntry { + err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) + g.Expect(err).ShouldNot(HaveOccurred()) + + return config.Spec.Sync.SyncOnly + }, timeout).Should(HaveLen(2)) + + By("StorageClass should not be in SyncOnly") + Expect(slices.IndexFunc(config.Spec.Sync.SyncOnly, func(s v1alpha1.SyncOnlyEntry) bool { + return s.Kind == "Ingress" + })).Should(Equal(-1)) + }) + It("Should have 1 syncOnly elements in config", func() { + Kubectl("delete", "-f", case1ConstraintStorageclassYaml, "--ignore-not-found") + + config := &v1alpha1.Config{} + + Eventually(func(g Gomega) []v1alpha1.SyncOnlyEntry { + err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) + g.Expect(err).ShouldNot(HaveOccurred()) + + return config.Spec.Sync.SyncOnly + }, timeout).Should(HaveLen(1)) + + By("StorageClass should not be in SyncOnly") + Expect(slices.IndexFunc(config.Spec.Sync.SyncOnly, func(s v1alpha1.SyncOnlyEntry) bool { + return s.Kind == "StorageClass" + })).Should(Equal(-1)) + }) + It("Should still have 1 syncOnly elements in config when Pod constraint is deleted", func() { + Kubectl("delete", "-f", case1ConstraintPodYaml, "--ignore-not-found") + config := &v1alpha1.Config{} + + Eventually(func(g Gomega) []v1alpha1.SyncOnlyEntry { + err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) + g.Expect(err).ShouldNot(HaveOccurred()) + + return config.Spec.Sync.SyncOnly + }, timeout).Should(HaveLen(1)) + + By("Pod should exist in SyncOnly because case1ConstraintPod2 yet exist") + Expect(slices.IndexFunc(config.Spec.Sync.SyncOnly, func(s v1alpha1.SyncOnlyEntry) bool { + return s.Kind == "Pod" + })).ShouldNot(Equal(-1)) + }) + It("Should have 0 syncOnly elements in config ", func() { + Kubectl("delete", "-f", case1ConstraintPod2Yaml, "--ignore-not-found") + config := &v1alpha1.Config{} + + Eventually(func(g Gomega) []v1alpha1.SyncOnlyEntry { + err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) + g.Expect(err).ShouldNot(HaveOccurred()) + + return config.Spec.Sync.SyncOnly + }, timeout).Should(BeNil()) + }) + }) AfterAll(func() { Kubectl("delete", "ns", allowNamespace, "--ignore-not-found", "--grace-period=1") Kubectl("delete", "ns", denyNamespace, "--ignore-not-found", "--grace-period=1") Kubectl("delete", "-f", case1ConstraintPodYaml, "--ignore-not-found") Kubectl("delete", "-f", case1ConstraintIngressYaml, "--ignore-not-found") Kubectl("delete", "-f", case1ConstraintStorageclassYaml, "--ignore-not-found") + Kubectl("delete", "-f", case1ConstraintPod2Yaml, "--ignore-not-found") Kubectl("delete", "-f", case1GatekeeperYaml, "--ignore-not-found") ctlDeployment := GetWithTimeout(clientHubDynamic, deploymentGVR, "gatekeeper-controller-manager", gatekeeperNamespace, false, 60)