diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 1c956115b..5ced2fbef 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -86,7 +86,47 @@ jobs: kubectl -n mygatekeeper wait deployment/gatekeeper-operator-controller --for condition=Available --timeout=90s kubectl -n mygatekeeper logs deployment/gatekeeper-operator-controller -c manager -f > operator.log & make test-e2e NAMESPACE=mygatekeeper - kubectl delete --wait namespace mygatekeeper + + - name: Debug + if: ${{ failure() }} + run: | + echo "::group::Operator Logs" + cat operator.log + echo "::endgroup::" + + configsync-e2e-test: + name: Run configsync e2e tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history for all tags and branches + + - uses: actions/setup-go@v3 + with: + go-version-file: go.mod + + - name: Download binaries + run: | + make download-binaries + + - name: Create K8s KinD Cluster + run: | + kind version + make test-cluster + + - name: Build and Push Test Container Image to KIND node + run: | + make docker-build IMG=localhost:5000/gatekeeper-operator:$GITHUB_SHA + kind load docker-image localhost:5000/gatekeeper-operator:$GITHUB_SHA + + - name: E2E Tests + run: | + make deploy-ci NAMESPACE=mygatekeeper IMG=localhost:5000/gatekeeper-operator:$GITHUB_SHA + kubectl -n mygatekeeper wait deployment/gatekeeper-operator-controller --for condition=Available --timeout=90s + kubectl -n mygatekeeper logs deployment/gatekeeper-operator-controller -c manager -f > operator.log & + make test-e2e NAMESPACE=mygatekeeper LABEL_FILTER=config - name: Debug if: ${{ failure() }} diff --git a/.gitignore b/.gitignore index 874c0daea..ec1366373 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ testbin/* !vendor/**/zz_generated.* ci-tools/ + +.vscode/* diff --git a/Makefile b/Makefile index 8b2d84bf9..49def4a89 100644 --- a/Makefile +++ b/Makefile @@ -141,8 +141,9 @@ tidy: ## Run go mod tidy .PHONY: test test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" GOFLAGS=$(GOFLAGS) go test ./... -coverprofile cover.out + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" GOFLAGS=$(GOFLAGS) go test $(go list ./... | grep -v /test/) -coverprofile cover.out +LABEL_FILTER=gatekeeper-controller .PHONY: test-e2e test-e2e: e2e-dependencies generate fmt vet ## Run e2e tests, using the configured Kubernetes cluster in ~/.kube/config GOFLAGS=$(GOFLAGS) USE_EXISTING_CLUSTER=true $(GINKGO) --trace --fail-fast --label-filter="$(LABEL_FILTER)" ./test/e2e -- --namespace="$(NAMESPACE)" --timeout="5m" --delete-timeout="10m" @@ -163,6 +164,17 @@ download-binaries: kustomize go-bindata envtest controller-gen curl -sSLO https://github.com/bats-core/bats-core/archive/v${BATS_VERSION}.tar.gz && tar -zxvf v${BATS_VERSION}.tar.gz && bash bats-core-${BATS_VERSION}/install.sh $(PWD)/ci-tools rm -rf bats-core-${BATS_VERSION} v${BATS_VERSION}.tar.gz +DEV_IMG=localhost:5000/gatekeeper-operator:dev +.PHONY: kind-bootstrap-cluster +kind-bootstrap-cluster: test-cluster dev-build + kind load docker-image $(DEV_IMG) + $(MAKE) deploy-ci NAMESPACE=$(NAMESPACE) IMG=$(DEV_IMG) + kubectl -n $(NAMESPACE) wait deployment/gatekeeper-operator-controller --for condition=Available --timeout=90s + +.PHONY: dev-build +dev-build: export DOCKER_DEFAULT_PLATFORM=linux/amd64 +dev-build: ## Build docker image with the manager for Mac user + $(DOCKER) build --build-arg GOOS=linux --build-arg GOARCH=amd64 --build-arg LDFLAGS=${LDFLAGS} -t ${DEV_IMG} . ##@ Build .PHONY: build diff --git a/api/v1alpha1/gatekeeper_types.go b/api/v1alpha1/gatekeeper_types.go index 72675044c..ee7458d39 100644 --- a/api/v1alpha1/gatekeeper_types.go +++ b/api/v1alpha1/gatekeeper_types.go @@ -140,12 +140,13 @@ const ( LogLevelError LogLevelMode = "ERROR" ) -// +kubebuilder:validation:Enum:=Enabled;Disabled +// +kubebuilder:validation:Enum:=Enabled;Disabled;Automatic type AuditFromCacheMode string const ( - AuditFromCacheEnabled AuditFromCacheMode = "Enabled" - AuditFromCacheDisabled AuditFromCacheMode = "Disabled" + AuditFromCacheEnabled AuditFromCacheMode = "Enabled" + AuditFromCacheDisabled AuditFromCacheMode = "Disabled" + AuditFromCacheAutomatic AuditFromCacheMode = "Automatic" ) // +kubebuilder:validation:Enum:=Enabled;Disabled diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index bf671656c..eded79c11 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -33,4 +33,10 @@ 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/bundle/manifests/operator.gatekeeper.sh_gatekeepers.yaml b/bundle/manifests/operator.gatekeeper.sh_gatekeepers.yaml index 7ffedce7b..1b0cfe1d9 100644 --- a/bundle/manifests/operator.gatekeeper.sh_gatekeepers.yaml +++ b/bundle/manifests/operator.gatekeeper.sh_gatekeepers.yaml @@ -867,6 +867,7 @@ spec: enum: - Enabled - Disabled + - Automatic type: string auditInterval: type: string diff --git a/config/crd/bases/operator.gatekeeper.sh_gatekeepers.yaml b/config/crd/bases/operator.gatekeeper.sh_gatekeepers.yaml index a5b84d840..49574946a 100644 --- a/config/crd/bases/operator.gatekeeper.sh_gatekeepers.yaml +++ b/config/crd/bases/operator.gatekeeper.sh_gatekeepers.yaml @@ -867,6 +867,7 @@ spec: enum: - Enabled - Disabled + - Automatic type: string auditInterval: type: string diff --git a/controllers/constraintstatus_controller.go b/controllers/constraintstatus_controller.go new file mode 100644 index 000000000..fe108830f --- /dev/null +++ b/controllers/constraintstatus_controller.go @@ -0,0 +1,260 @@ +package controllers + +import ( + "context" + "strings" + "time" + + "github.com/go-logr/logr" + "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" + "k8s.io/client-go/kubernetes" + "k8s.io/utils/strings/slices" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var ControllerName = "contraintstatus_reconciler" + +type discoveryInfo struct { + apiResourceList []*metav1.APIResourceList + discoveryLastRefreshed time.Time +} + +type ConstraintStatusReconciler struct { + client.Client + Log logr.Logger + DynamicClient dynamic.Interface + ClientSet *kubernetes.Clientset + Scheme *runtime.Scheme + Namespace string + MaxConcurrentReconciles uint + discoveryInfo +} + +// SetupWithManager sets up the controller with the Manager. +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)}). + // watch Config resrouce as secondary + Named(ControllerName). + For(&v1beta1.ConstraintPodStatus{}, + builder.WithPredicates(predicate.Funcs{ + // Execute this reconcile func when it is audit-constraintStatuspod + // because a constraint creates 4 constraintStatuspods + CreateFunc: func(e event.CreateEvent) bool { + obj := e.Object.(*v1beta1.ConstraintPodStatus) + + return slices.Contains(obj.Status.Operations, "audit") + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObj := e.ObjectOld.(*v1beta1.ConstraintPodStatus) + newObj := e.ObjectNew.(*v1beta1.ConstraintPodStatus) + + return slices.Contains(newObj.Status.Operations, "audit") && + oldObj.Status.ObservedGeneration != newObj.Status.ObservedGeneration + }, + DeleteFunc: func(e event.DeleteEvent) bool { + obj := e.Object.(*v1beta1.ConstraintPodStatus) + + return slices.Contains(obj.Status.Operations, "audit") + }, + }, + )). + Complete(r) +} + +// User set gatekeeper.spec.audit.auditFromCache to Automatic, this reconcile function +// collect all constraints and add found kinds which is used in contraint 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") + + // Get config or create if not exist + config := &v1alpha1.Config{} + err := r.Get(ctx, types.NamespacedName{ + Namespace: r.Namespace, + Name: "config", + }, config) + + if apierrors.IsNotFound(err) { + config = &v1alpha1.Config{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config", + Namespace: r.Namespace, + }, + } + err = r.Create(ctx, config) + + if err != nil { + return reconcile.Result{}, err + } + } + + contraintPodStatus := &v1beta1.ConstraintPodStatus{} + err = r.Get(ctx, request.NamespacedName, contraintPodStatus) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Error(err, "Cannot find any contraintStatus") + + return reconcile.Result{}, nil + } + // Requeue + return reconcile.Result{}, err + } + + labels := contraintPodStatus.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 + for _, entry := range config.Spec.Sync.SyncOnly { + table[entry] = true + } + + constraintGVR := schema.GroupVersionResource{ + Group: "constraints.gatekeeper.sh", + Version: "v1beta1", + Resource: strings.ToLower(constraintKind), + } + + constraint, err := r.DynamicClient.Resource(constraintGVR).Get(ctx, constraintName, metav1.GetOptions{}) + if err != nil { + return reconcile.Result{}, err + } + + constraintMatchKinds, _, err := unstructured.NestedSlice(constraint.Object, "spec", "match", "kinds") + if err != nil { + return reconcile.Result{}, err + } + + contraintSyncOnlyEntries, err := r.getSyncOnlys(constraintMatchKinds) + if err != nil { + log.Error(err, "Error to get matching kind and apigroup") + + return reconcile.Result{}, nil + } + + for _, ce := range *contraintSyncOnlyEntries { + table[ce] = true + } + + syncOnlys := []v1alpha1.SyncOnlyEntry{} + for key := range table { + syncOnlys = append(syncOnlys, key) + } + + config.Spec.Sync.SyncOnly = syncOnlys + err = r.Update(ctx, config, &client.UpdateOptions{}) + + if err != nil { + return reconcile.Result{}, err + } + + return reconcile.Result{}, nil +} + +func (r *ConstraintStatusReconciler) getSyncOnlys(constraintMatchKinds []interface{}) ( + *[]v1alpha1.SyncOnlyEntry, error, +) { + syncOnlys := []v1alpha1.SyncOnlyEntry{} + + for _, kind := range constraintMatchKinds { + newKind := kind.(map[string]interface{}) + apiGroups := newKind["apiGroups"].([]interface{}) + kindsInKinds := newKind["kinds"].([]interface{}) + + for _, apiGroup := range apiGroups { + for _, kindkind := range kindsInKinds { + version, err := r.getAPIVersion(kindkind.(string), apiGroup.(string)) + if err != nil { + return nil, err + } + + syncOnlys = append(syncOnlys, v1alpha1.SyncOnlyEntry{ + Group: apiGroup.(string), + Version: version, + Kind: kindkind.(string), + }) + } + } + } + + return &syncOnlys, nil +} + +func (r *ConstraintStatusReconciler) getAPIVersion(kind string, apiGroup string) (string, error) { + // cool time(10 min) to refresh discoveries + if len(r.apiResourceList) == 0 || + r.discoveryLastRefreshed.Add(time.Minute*10).Before(time.Now()) { + err := r.refreshDiscoveryInfo() + r.discoveryLastRefreshed = time.Now() + + if err != nil { + return "", err + } + } + + 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") + + return "", errors.New("Error in parse Group and version in getApiVersion function") + } + + group := groupVerison.Group + version := groupVerison.Version + // Consider groupversion == v1 or groupversion == app1/v1 + for _, apiResource := range resc.APIResources { + if apiResource.Kind == kind && group == apiGroup { + return version, nil + } + } + } + + // Get new discoveryInfo, when any resource is not found + err := r.refreshDiscoveryInfo() + r.discoveryLastRefreshed = time.Now() + + if err != nil { + return "", err + } + + return "", errors.New("Getting discovery has error") +} + +// Retrieve all groups and versions to add in config sync +// Constraints present only kind and group so this function helps to find the version +func (r *ConstraintStatusReconciler) refreshDiscoveryInfo() error { + discoveryClient := r.ClientSet.Discovery() + + apiList, err := discoveryClient.ServerPreferredResources() + if err != nil { + return err + } + + r.apiResourceList = apiList + + return nil +} diff --git a/deploy/gatekeeper-operator.yaml b/deploy/gatekeeper-operator.yaml index 7d72df453..20b63c5cd 100644 --- a/deploy/gatekeeper-operator.yaml +++ b/deploy/gatekeeper-operator.yaml @@ -874,6 +874,7 @@ spec: enum: - Enabled - Disabled + - Automatic type: string auditInterval: type: string diff --git a/go.mod b/go.mod index 4d66ff33f..10a2c4853 100644 --- a/go.mod +++ b/go.mod @@ -7,16 +7,23 @@ 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/pkg/errors v0.9.1 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d k8s.io/api v0.28.3 k8s.io/apiextensions-apiserver v0.28.3 k8s.io/apimachinery v0.28.3 k8s.io/client-go v0.28.3 + k8s.io/klog v1.0.0 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/controller-runtime v0.16.3 ) 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/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect @@ -30,6 +37,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/cel-go v0.16.1 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -43,16 +51,18 @@ 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 github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect @@ -60,14 +70,16 @@ 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/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiserver v0.28.3 // indirect k8s.io/component-base v0.28.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/go.sum b/go.sum index 0852247cf..43bed4ede 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,23 @@ +cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +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= +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= +github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= +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/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= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -12,13 +29,19 @@ github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +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= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -39,6 +62,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo= +github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -52,8 +77,13 @@ github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVe github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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/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/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/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= @@ -82,6 +112,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/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= @@ -95,23 +130,41 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/prometheus v0.35.0 h1:N93oX6BrJ2iP3UuE2Uz4Lt+5BkUpaFer3L9CbADzesc= +github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.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= @@ -141,6 +194,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -170,8 +225,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/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/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= @@ -182,6 +244,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -194,16 +257,21 @@ k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2E k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= +k8s.io/apiserver v0.28.3 h1:8Ov47O1cMyeDzTXz0rwcfIIGAP/dP7L8rWbEljRcg5w= +k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM= k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA= sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/main.go b/main.go index 204245b8b..443994b41 100644 --- a/main.go +++ b/main.go @@ -17,20 +17,17 @@ limitations under the License. package main import ( + "context" "flag" "fmt" "os" + "sync" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -39,7 +36,30 @@ import ( "github.com/gatekeeper/gatekeeper-operator/pkg/platform" "github.com/gatekeeper/gatekeeper-operator/pkg/util" "github.com/gatekeeper/gatekeeper-operator/pkg/version" + "github.com/open-policy-agent/gatekeeper/v3/apis/config/v1alpha1" + "github.com/open-policy-agent/gatekeeper/v3/apis/status/v1beta1" "github.com/pkg/errors" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + 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" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + toolsWatch "k8s.io/client-go/tools/watch" + ctrl "sigs.k8s.io/controller-runtime" + cacheRuntime "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" // +kubebuilder:scaffold:imports ) @@ -50,11 +70,19 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(operatorv1alpha1.AddToScheme(scheme)) + utilruntime.Must(v1beta1.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } +type ContraintStatusInstaller struct { + mu sync.Mutex + isInstalled bool + client client.Client +} + func main() { var metricsAddr string var enableLeaderElection bool @@ -73,6 +101,7 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) ctrl.Log.WithName("Gatekeeper Operator version").Info(fmt.Sprintf("%#v", version.Get())) + mainCtx := ctrl.SetupSignalHandler() metricsOptions := server.Options{ BindAddress: metricsAddr, @@ -120,6 +149,46 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Gatekeeper") os.Exit(1) } + + var wg sync.WaitGroup + + dynamicClient := dynamic.NewForConfigOrDie(mgr.GetConfig()) + wg.Add(1) + + c := ContraintStatusInstaller{isInstalled: false, client: mgr.GetClient()} + + 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: "contraintstatusController.gatekeeper.sh", + Cache: cacheRuntime.Options{ + ByObject: map[client.Object]cacheRuntime.ByObject{ + &v1beta1.ConstraintPodStatus{}: { + Transform: func(obj interface{}) (interface{}, error) { + contraintStatus := obj.(*v1beta1.ConstraintPodStatus) + // Only cache fields that are utilized by the controllers. + guttedObj := &v1beta1.ConstraintPodStatus{ + TypeMeta: contraintStatus.TypeMeta, + ObjectMeta: contraintStatus.ObjectMeta, + Status: v1beta1.ConstraintPodStatusStatus{ + Operations: contraintStatus.Status.Operations, + }, + } + return guttedObj, nil + }, + }, + }, + }, + }, namespace) // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { @@ -132,10 +201,14 @@ func main() { } setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + 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() } func gatekeeperNamespace(platformInfo platform.PlatformInfo) (string, error) { @@ -154,3 +227,181 @@ func gatekeeperNamespace(platformInfo platform.PlatformInfo) (string, error) { return ns, nil } + +func (c *ContraintStatusInstaller) addConstraintStatusManager(ctx context.Context, mgr manager.Manager, + dynamicClient *dynamic.DynamicClient, namespace string, +) error { + if err := (&controllers.ConstraintStatusReconciler{ + Client: mgr.GetClient(), + 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") + return err + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + return err + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + return err + } + if err := mgr.Start(ctx); err != nil { + setupLog.Error(err, "problem running ConstraintStatus manager") + + // On errors, the parent context (mainCtx) may not have closed, so cancel the child context. d + return err + } + + return nil +} + +func (c *ContraintStatusInstaller) getIsInstalled() bool { + c.mu.Lock() + defer c.mu.Unlock() + + return c.isInstalled +} + +func (c *ContraintStatusInstaller) 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.Error(err, "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) + if ok != nil { + return false + } + + conditions, _, err := unstructured.NestedSlice(crd, "status", "conditions") + if err != nil { + return false + } + + for _, condition := range conditions { + conditionObj, ok := condition.(map[string]interface{}) + if !ok { + return false + } + + conditionType := conditionObj["type"].(string) + if conditionType == string(v1.NamesAccepted) { + return true + } + } + + setupLog.V(1).Info("Constraintpodstatuses crd is not ready, Wait for crd status update") + + return false +} + +// 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, + 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}) + + for { + select { + case result := <-watcher.ResultChan(): + switch result.Type { //nolint:exhaustive + case watch.Modified, watch.Added: + if !c.getIsInstalled() { + isAutomaticSet := c.getAutomaticSet(mainCtx) + if !isAutomaticSet { + break + } + // Check constraintpodstatuses crd available + if !c.isApiAvailable(result) { + break + } + 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(): + ctxCancel() + return + } + } +} diff --git a/test/e2e/case1_audit_from_cache_test.go b/test/e2e/case1_audit_from_cache_test.go new file mode 100644 index 000000000..91143c505 --- /dev/null +++ b/test/e2e/case1_audit_from_cache_test.go @@ -0,0 +1,169 @@ +package e2e + +import ( + "time" + + . "github.com/gatekeeper/gatekeeper-operator/test/e2e/util" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/open-policy-agent/gatekeeper/v3/apis/config/v1alpha1" + "golang.org/x/exp/slices" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +const ( + case1GatekeeperYaml string = "../resources/case1_audit_from_cache/gatekeeper.yaml" + case1TemplateYaml string = "../resources/case1_audit_from_cache/template.yaml" + case1ConstraintPodYaml string = "../resources/case1_audit_from_cache/constraint-pod.yaml" + case1ConstraintPod2Yaml string = "../resources/case1_audit_from_cache/constraint-pod-2.yaml" + case1ConstraintIngressYaml string = "../resources/case1_audit_from_cache/constraint-ingress.yaml" + case1ConstraintStorageclassYaml string = "../resources/case1_audit_from_cache/constraint-storageclass.yaml" + case1PodYaml string = "../resources/case1_audit_from_cache/pod.yaml" + allowNamespace string = "case1-allow" + denyNamespace string = "case1-deny" +) + +var constraintGVR = schema.GroupVersionResource{ + Group: "constraints.gatekeeper.sh", + Version: "v1beta1", + Resource: "case1template", +} + +var templateGVR = schema.GroupVersionResource{ + Group: "templates.gatekeeper.sh", + Version: "v1", + Resource: "constrainttemplates", +} + +var _ = Describe("Test auditFromCache", Label("config"), Ordered, func() { + BeforeAll(func() { + if !useExistingCluster() { + Skip("Test requires existing cluster. Set environment variable USE_EXISTING_CLUSTER=true and try again.") + } + + By("Create namespaces to compare") + Kubectl("create", "ns", allowNamespace) + Kubectl("create", "ns", denyNamespace) + + By("Create a gatekeeper resource") + _, err := KubectlWithOutput("apply", "-f", case1GatekeeperYaml) + Expect(err).ShouldNot(HaveOccurred()) + // 150 secs are needed when this spec after gatekeeper-controller test + ctlDeployment := GetWithTimeout(clientHubDynamic, deploymentGVR, + "gatekeeper-controller-manager", gatekeeperNamespace, true, 150) + Expect(ctlDeployment).NotTo(BeNil()) + + Eventually(func(g Gomega) { + auditDeployment := GetWithTimeout(clientHubDynamic, deploymentGVR, + "gatekeeper-audit", gatekeeperNamespace, true, 60) + g.Expect(auditDeployment).NotTo(BeNil()) + + availableReplicas, _, err := unstructured.NestedInt64(auditDeployment.Object, "status", "availableReplicas") + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(availableReplicas).Should(BeNumerically(">", 0)) + }, 2*time.Minute, 10*time.Second).Should(Succeed()) + + _, err = KubectlWithOutput("apply", "-f", case1TemplateYaml) + Expect(err).ShouldNot(HaveOccurred()) + template := GetWithTimeout(clientHubDynamic, templateGVR, "case1template", "", true, 60) + Expect(template).NotTo(BeNil()) + + Eventually(func() error { + _, err = KubectlWithOutput("apply", "-f", case1ConstraintStorageclassYaml) + + return err + }, timeout).ShouldNot(HaveOccurred()) + storageclass := GetWithTimeout(clientHubDynamic, constraintGVR, "case1-storageclass-deny", "", true, 60) + Expect(storageclass).NotTo(BeNil()) + + Eventually(func() error { + _, err = KubectlWithOutput("apply", "-f", case1ConstraintPodYaml) + + return err + }, timeout).ShouldNot(HaveOccurred()) + pod := GetWithTimeout(clientHubDynamic, constraintGVR, "case1-pod-deny", "", true, 60) + Expect(pod).NotTo(BeNil()) + + Eventually(func() error { + _, err = KubectlWithOutput("apply", "-f", case1ConstraintPod2Yaml) + + return err + }, timeout).ShouldNot(HaveOccurred()) + pod2 := GetWithTimeout(clientHubDynamic, constraintGVR, "case1-pod-deny-2", "", true, 60) + Expect(pod2).NotTo(BeNil()) + + Eventually(func() error { + _, err = KubectlWithOutput("apply", "-f", case1ConstraintIngressYaml) + + return err + }, timeout).ShouldNot(HaveOccurred()) + ingress := GetWithTimeout(clientHubDynamic, constraintGVR, "case1-ingress-deny", "", true, 60) + Expect(ingress).NotTo(BeNil()) + }) + Describe("Gatekeeper with auditFromCache=Automatic create syncOnly config", func() { + It("should create config resource with syncOnly includes pod, ingress, storageclass", func() { + config := &v1alpha1.Config{} + + By("config syncOnly should have 3 elements, duplicated should be deleted") + Eventually(func() error { + err := K8sClient.Get(ctx, types.NamespacedName{Name: "config", Namespace: gatekeeperNamespace}, config) + + return err + }, timeout*2).ShouldNot(HaveOccurred()) + + 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)) + + expectedSyncOnly := map[string]v1alpha1.SyncOnlyEntry{ + "Ingress": { + Group: "networking.k8s.io", + Kind: "Ingress", + Version: "v1", + }, + "Pod": { + Kind: "Pod", + Version: "v1", + }, + "StorageClass": { + Group: "storage.k8s.io", + Version: "v1", + Kind: "StorageClass", + }, + } + for key, val := range expectedSyncOnly { + foundSyncOnly := slices.IndexFunc(config.Spec.Sync.SyncOnly, func(s v1alpha1.SyncOnlyEntry) bool { + return s.Kind == key + }) + Expect(config.Spec.Sync.SyncOnly[foundSyncOnly]).Should(BeEquivalentTo(val)) + } + }) + It("Should error message shows cached pod list", func() { + _, err := KubectlWithOutput("apply", "-f", case1PodYaml, "-n", allowNamespace) + Expect(err).ShouldNot(HaveOccurred()) + output, err := KubectlWithOutput("apply", "-f", case1PodYaml, "-n", denyNamespace) + Expect(err).Should(HaveOccurred()) + Expect(output). + Should(ContainSubstring("cached data: {\"case1-pod\": {\"apiVersion\": \"v1\", \"kind\": \"Pod\"")) + }) + }) + 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", case1GatekeeperYaml, "--ignore-not-found") + ctlDeployment := GetWithTimeout(clientHubDynamic, deploymentGVR, + "gatekeeper-controller-manager", gatekeeperNamespace, false, 60) + Expect(ctlDeployment).Should(BeNil()) + auditDeployment := GetWithTimeout(clientHubDynamic, deploymentGVR, + "gatekeeper-audit", gatekeeperNamespace, false, 60) + Expect(auditDeployment).Should(BeNil()) + }) +}) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 3433c8ac2..af36d595b 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -18,7 +18,9 @@ package e2e import ( "context" + "fmt" "os" + "os/user" "path/filepath" "testing" @@ -28,15 +30,20 @@ import ( extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/klog" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" operatorv1alpha1 "github.com/gatekeeper/gatekeeper-operator/api/v1alpha1" + "github.com/open-policy-agent/gatekeeper/v3/apis/config/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -44,16 +51,17 @@ import ( // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. var ( - cfg *rest.Config - K8sClient client.Client - testEnv *envtest.Environment - affinityPod *corev1.Pod - affinityNode *corev1.Node + cfg *rest.Config + K8sClient client.Client + testEnv *envtest.Environment + affinityPod *corev1.Pod + affinityNode *corev1.Node + clientHubDynamic dynamic.Interface + deploymentGVR schema.GroupVersionResource ) func TestE2e(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Controller Suite") } @@ -88,6 +96,12 @@ var _ = BeforeSuite(func() { Expect(labelNode(affinityNode)).Should(Succeed()) createAffinityPod() } + + err = v1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + clientHubDynamic = NewKubeClientDynamic("", "", "") + deploymentGVR = schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} }) var _ = AfterSuite(func() { @@ -168,3 +182,60 @@ func loadAffinityPodFromFile(namespace string) (*corev1.Pod, error) { pod.ObjectMeta.Namespace = namespace return pod, err } + +func NewKubeClientDynamic(url, kubeconfig, context string) dynamic.Interface { + klog.V(5).Infof("Create kubeclient dynamic for url %s using kubeconfig path %s\n", url, kubeconfig) + + config, err := LoadConfig(url, kubeconfig, context) + if err != nil { + panic(err) + } + + clientset, err := dynamic.NewForConfig(config) + if err != nil { + panic(err) + } + + return clientset +} + +func LoadConfig(url, kubeconfig, context string) (*rest.Config, error) { + if kubeconfig == "" { + kubeconfig = os.Getenv("KUBECONFIG") + } + + klog.V(5).Infof("Kubeconfig path %s\n", kubeconfig) + + // If we have an explicit indication of where the kubernetes config lives, read that. + if kubeconfig != "" { + if context == "" { + return clientcmd.BuildConfigFromFlags(url, kubeconfig) + } + + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, + &clientcmd.ConfigOverrides{ + CurrentContext: context, + }).ClientConfig() + } + + // If not, try the in-cluster config. + if c, err := rest.InClusterConfig(); err == nil { + return c, nil + } + + // If no in-cluster config, try the default location in the user's home directory. + if usr, err := user.Current(); err == nil { + klog.V(5).Infof( + "clientcmd.BuildConfigFromFlags for url %s using %s\n", + url, + filepath.Join(usr.HomeDir, ".kube", "config"), + ) + + if c, err := clientcmd.BuildConfigFromFlags("", filepath.Join(usr.HomeDir, ".kube", "config")); err == nil { + return c, nil + } + } + + return nil, fmt.Errorf("could not create a valid kubeconfig") +} diff --git a/test/e2e/gatekeeper_controller_test.go b/test/e2e/gatekeeper_controller_test.go index 08295622c..ff44ab18b 100644 --- a/test/e2e/gatekeeper_controller_test.go +++ b/test/e2e/gatekeeper_controller_test.go @@ -81,7 +81,7 @@ func initializeGlobals() { } } -var _ = Describe("Gatekeeper", func() { +var _ = Describe("Gatekeeper", Label("gatekeeper-controller"), func() { BeforeEach(func() { if !useExistingCluster() { Skip("Test requires existing cluster. Set environment variable USE_EXISTING_CLUSTER=true and try again.") @@ -106,7 +106,7 @@ var _ = Describe("Gatekeeper", func() { }, deleteTimeout, pollInterval).Should(BeTrue()) }) - Describe("Overriding CR", func() { + Describe("Overriding CR", Ordered, func() { It("Creating an empty gatekeeper contains default values", func() { gatekeeper := emptyGatekeeper() err := loadGatekeeperFromFile(gatekeeper, "gatekeeper_empty.yaml") diff --git a/test/e2e/options.go b/test/e2e/options.go index 04bcaded7..2ca05d98f 100644 --- a/test/e2e/options.go +++ b/test/e2e/options.go @@ -23,10 +23,12 @@ import ( "github.com/gatekeeper/gatekeeper-operator/pkg/util" ) -var gatekeeperNamespace string -var pollInterval time.Duration -var timeout time.Duration -var deleteTimeout time.Duration +var ( + gatekeeperNamespace string + pollInterval time.Duration + timeout time.Duration + deleteTimeout time.Duration +) func init() { flag.StringVar(&gatekeeperNamespace, "namespace", util.DefaultGatekeeperNamespace, "The namespace to run tests") diff --git a/test/e2e/util/util.go b/test/e2e/util/util.go index de25a3f29..681862fb3 100644 --- a/test/e2e/util/util.go +++ b/test/e2e/util/util.go @@ -17,10 +17,20 @@ limitations under the License. package util import ( + "context" + "fmt" + "os/exec" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" admregv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" ) type defaultConfig struct { @@ -89,3 +99,64 @@ var DefaultDeployment = defaultConfig{ }, }, } + +// GetWithTimeout keeps polling to get the object for timeout seconds until wantFound is met +// (true for found, false for not found) +func GetWithTimeout( + clientHubDynamic dynamic.Interface, + gvr schema.GroupVersionResource, + name, namespace string, + wantFound bool, + timeout int, +) *unstructured.Unstructured { + if timeout < 1 { + timeout = 1 + } + + var obj *unstructured.Unstructured + + EventuallyWithOffset(1, func() error { + var err error + namespace := clientHubDynamic.Resource(gvr).Namespace(namespace) + + obj, err = namespace.Get(context.TODO(), name, metav1.GetOptions{}) + if wantFound && err != nil { + return err + } + + if !wantFound && err == nil { + return fmt.Errorf("expected to return IsNotFound error") + } + + if !wantFound && err != nil && !errors.IsNotFound(err) { + return err + } + + return nil + }, timeout, 1).Should(BeNil()) + + if wantFound { + return obj + } + + return nil +} + +// Kubectl execute kubectl cli +func Kubectl(args ...string) { + cmd := exec.Command("kubectl", args...) + + err := cmd.Start() + if err != nil { + Fail(fmt.Sprintf("Error: %v", err)) + } +} + +// KubectlWithOutput execute kubectl cli and return output and error +func KubectlWithOutput(args ...string) (string, error) { + output, err := exec.Command("kubectl", args...).CombinedOutput() + //nolint:forbidigo + fmt.Println(string(output)) + + return string(output), err +} diff --git a/test/resources/case1_audit_from_cache/constraint-ingress.yaml b/test/resources/case1_audit_from_cache/constraint-ingress.yaml new file mode 100644 index 000000000..085a73eb2 --- /dev/null +++ b/test/resources/case1_audit_from_cache/constraint-ingress.yaml @@ -0,0 +1,9 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: Case1Template +metadata: + name: case1-ingress-deny +spec: + match: + kinds: + - apiGroups: ["networking.k8s.io"] + kinds: ["Ingress"] \ No newline at end of file diff --git a/test/resources/case1_audit_from_cache/constraint-pod-2.yaml b/test/resources/case1_audit_from_cache/constraint-pod-2.yaml new file mode 100644 index 000000000..42547aa97 --- /dev/null +++ b/test/resources/case1_audit_from_cache/constraint-pod-2.yaml @@ -0,0 +1,10 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: Case1Template +metadata: + name: case1-pod-deny-2 +spec: + match: + excludedNamespaces: ["case1-allow"] + kinds: + - apiGroups: [""] + kinds: ["Pod"] \ No newline at end of file diff --git a/test/resources/case1_audit_from_cache/constraint-pod.yaml b/test/resources/case1_audit_from_cache/constraint-pod.yaml new file mode 100644 index 000000000..6350dac2a --- /dev/null +++ b/test/resources/case1_audit_from_cache/constraint-pod.yaml @@ -0,0 +1,10 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: Case1Template +metadata: + name: case1-pod-deny +spec: + match: + excludedNamespaces: ["case1-allow"] + kinds: + - apiGroups: [""] + kinds: ["Pod"] \ No newline at end of file diff --git a/test/resources/case1_audit_from_cache/constraint-storageclass.yaml b/test/resources/case1_audit_from_cache/constraint-storageclass.yaml new file mode 100644 index 000000000..711a56ee5 --- /dev/null +++ b/test/resources/case1_audit_from_cache/constraint-storageclass.yaml @@ -0,0 +1,9 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: Case1Template +metadata: + name: case1-storageclass-deny +spec: + match: + kinds: + - apiGroups: ["storage.k8s.io"] + kinds: ["StorageClass"] \ No newline at end of file diff --git a/test/resources/case1_audit_from_cache/gatekeeper.yaml b/test/resources/case1_audit_from_cache/gatekeeper.yaml new file mode 100644 index 000000000..bdfbe89bd --- /dev/null +++ b/test/resources/case1_audit_from_cache/gatekeeper.yaml @@ -0,0 +1,11 @@ +apiVersion: operator.gatekeeper.sh/v1alpha1 +kind: Gatekeeper +metadata: + name: gatekeeper +spec: + # Add fields here + audit: + replicas: 2 + logLevel: DEBUG + auditInterval: 20s + auditFromCache: Automatic diff --git a/test/resources/case1_audit_from_cache/pod.yaml b/test/resources/case1_audit_from_cache/pod.yaml new file mode 100644 index 000000000..0c61ff47f --- /dev/null +++ b/test/resources/case1_audit_from_cache/pod.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: case1-pod +spec: + containers: + - name: nginx + image: nginx:1.14.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 \ No newline at end of file diff --git a/test/resources/case1_audit_from_cache/template.yaml b/test/resources/case1_audit_from_cache/template.yaml new file mode 100644 index 000000000..937be06c9 --- /dev/null +++ b/test/resources/case1_audit_from_cache/template.yaml @@ -0,0 +1,20 @@ +apiVersion: templates.gatekeeper.sh/v1 +kind: ConstraintTemplate +metadata: + name: case1template +spec: + crd: + spec: + names: + kind: Case1Template + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package case1template + + violation[{"msg": msg}] { + 1 > 0 + msg := sprintf("This is test %v",["hi"]) + }{ + msg := sprintf("cached data: %v",[data.inventory.namespace["case1-allow"]["v1"]["Pod"]]) + } \ No newline at end of file