ValidatingAdmissionPolicy controller for Type Checking (#117377)
* [API REVIEW] ValidatingAdmissionPolicyStatucController config. worker count. * ValidatingAdmissionPolicyStatus controller. * remove CEL typechecking from API server. * fix initializer tests. * remove type checking integration tests from API server integration tests. * validatingadmissionpolicy-status options. * grant access to VAP controller. * add defaulting unit test. * generated: ./hack/update-codegen.sh * add OWNERS for VAP status controller. * type checking test case.
This commit is contained in:
@@ -531,6 +531,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,K
|
|||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ServiceController
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ServiceController
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,StatefulSetController
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,StatefulSetController
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,TTLAfterFinishedController
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,TTLAfterFinishedController
|
||||||
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ValidatingAdmissionPolicyStatusController
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,LegacySATokenCleanerConfiguration,CleanUpPeriod
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,LegacySATokenCleanerConfiguration,CleanUpPeriod
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,ConcurrentNamespaceSyncs
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,ConcurrentNamespaceSyncs
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,NamespaceSyncPeriod
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,NamespaceSyncPeriod
|
||||||
@@ -567,6 +568,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,S
|
|||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,SAControllerConfiguration,ServiceAccountKeyFile
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,SAControllerConfiguration,ServiceAccountKeyFile
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,StatefulSetControllerConfiguration,ConcurrentStatefulSetSyncs
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,StatefulSetControllerConfiguration,ConcurrentStatefulSetSyncs
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,TTLAfterFinishedControllerConfiguration,ConcurrentTTLSyncs
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,TTLAfterFinishedControllerConfiguration,ConcurrentTTLSyncs
|
||||||
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ValidatingAdmissionPolicyStatusControllerConfiguration,ConcurrentPolicySyncs
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableDynamicProvisioning
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableDynamicProvisioning
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableHostPathProvisioning
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableHostPathProvisioning
|
||||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,FlexVolumePluginDir
|
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,FlexVolumePluginDir
|
||||||
|
@@ -35,7 +35,6 @@ import (
|
|||||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
|
||||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/apiserver/pkg/server/egressselector"
|
"k8s.io/apiserver/pkg/server/egressselector"
|
||||||
@@ -45,7 +44,6 @@ import (
|
|||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
clientgoinformers "k8s.io/client-go/informers"
|
clientgoinformers "k8s.io/client-go/informers"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
k8sscheme "k8s.io/client-go/kubernetes/scheme"
|
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/util/keyutil"
|
"k8s.io/client-go/util/keyutil"
|
||||||
cliflag "k8s.io/component-base/cli/flag"
|
cliflag "k8s.io/component-base/cli/flag"
|
||||||
@@ -285,8 +283,7 @@ func CreateKubeAPIServerConfig(opts options.CompletedOptions) (
|
|||||||
CloudConfigFile: opts.CloudProvider.CloudConfigFile,
|
CloudConfigFile: opts.CloudProvider.CloudConfigFile,
|
||||||
}
|
}
|
||||||
serviceResolver := buildServiceResolver(opts.EnableAggregatorRouting, genericConfig.LoopbackClientConfig.Host, versionedInformers)
|
serviceResolver := buildServiceResolver(opts.EnableAggregatorRouting, genericConfig.LoopbackClientConfig.Host, versionedInformers)
|
||||||
schemaResolver := resolver.NewDefinitionsSchemaResolver(k8sscheme.Scheme, genericConfig.OpenAPIConfig.GetDefinitions)
|
pluginInitializers, admissionPostStartHook, err := admissionConfig.New(proxyTransport, genericConfig.EgressSelector, serviceResolver, genericConfig.TracerProvider)
|
||||||
pluginInitializers, admissionPostStartHook, err := admissionConfig.New(proxyTransport, genericConfig.EgressSelector, serviceResolver, genericConfig.TracerProvider, schemaResolver)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, fmt.Errorf("failed to create admission plugin initializer: %v", err)
|
return nil, nil, nil, fmt.Errorf("failed to create admission plugin initializer: %v", err)
|
||||||
}
|
}
|
||||||
|
@@ -484,6 +484,9 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc
|
|||||||
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.LegacyServiceAccountTokenCleanUp) {
|
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.LegacyServiceAccountTokenCleanUp) {
|
||||||
register(names.LegacyServiceAccountTokenCleanerController, startLegacySATokenCleaner)
|
register(names.LegacyServiceAccountTokenCleanerController, startLegacySATokenCleaner)
|
||||||
}
|
}
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.ValidatingAdmissionPolicy) {
|
||||||
|
register("validatingadmissionpolicy-status-controller", startValidatingAdmissionPolicyStatusController)
|
||||||
|
}
|
||||||
|
|
||||||
return controllers
|
return controllers
|
||||||
}
|
}
|
||||||
|
@@ -87,6 +87,7 @@ type KubeControllerManagerOptions struct {
|
|||||||
ResourceQuotaController *ResourceQuotaControllerOptions
|
ResourceQuotaController *ResourceQuotaControllerOptions
|
||||||
SAController *SAControllerOptions
|
SAController *SAControllerOptions
|
||||||
TTLAfterFinishedController *TTLAfterFinishedControllerOptions
|
TTLAfterFinishedController *TTLAfterFinishedControllerOptions
|
||||||
|
ValidatingAdmissionPolicyStatusController *ValidatingAdmissionPolicyStatusControllerOptions
|
||||||
|
|
||||||
SecureServing *apiserveroptions.SecureServingOptionsWithLoopback
|
SecureServing *apiserveroptions.SecureServingOptionsWithLoopback
|
||||||
Authentication *apiserveroptions.DelegatingAuthenticationOptions
|
Authentication *apiserveroptions.DelegatingAuthenticationOptions
|
||||||
@@ -186,6 +187,9 @@ func NewKubeControllerManagerOptions() (*KubeControllerManagerOptions, error) {
|
|||||||
TTLAfterFinishedController: &TTLAfterFinishedControllerOptions{
|
TTLAfterFinishedController: &TTLAfterFinishedControllerOptions{
|
||||||
&componentConfig.TTLAfterFinishedController,
|
&componentConfig.TTLAfterFinishedController,
|
||||||
},
|
},
|
||||||
|
ValidatingAdmissionPolicyStatusController: &ValidatingAdmissionPolicyStatusControllerOptions{
|
||||||
|
&componentConfig.ValidatingAdmissionPolicyStatusController,
|
||||||
|
},
|
||||||
SecureServing: apiserveroptions.NewSecureServingOptions().WithLoopback(),
|
SecureServing: apiserveroptions.NewSecureServingOptions().WithLoopback(),
|
||||||
Authentication: apiserveroptions.NewDelegatingAuthenticationOptions(),
|
Authentication: apiserveroptions.NewDelegatingAuthenticationOptions(),
|
||||||
Authorization: apiserveroptions.NewDelegatingAuthorizationOptions(),
|
Authorization: apiserveroptions.NewDelegatingAuthorizationOptions(),
|
||||||
@@ -261,6 +265,7 @@ func (s *KubeControllerManagerOptions) Flags(allControllers []string, disabledBy
|
|||||||
s.ResourceQuotaController.AddFlags(fss.FlagSet(names.ResourceQuotaController))
|
s.ResourceQuotaController.AddFlags(fss.FlagSet(names.ResourceQuotaController))
|
||||||
s.SAController.AddFlags(fss.FlagSet(names.ServiceAccountController))
|
s.SAController.AddFlags(fss.FlagSet(names.ServiceAccountController))
|
||||||
s.TTLAfterFinishedController.AddFlags(fss.FlagSet(names.TTLAfterFinishedController))
|
s.TTLAfterFinishedController.AddFlags(fss.FlagSet(names.TTLAfterFinishedController))
|
||||||
|
s.ValidatingAdmissionPolicyStatusController.AddFlags(fss.FlagSet(names.ValidatingAdmissionPolicyStatusController))
|
||||||
|
|
||||||
s.Metrics.AddFlags(fss.FlagSet("metrics"))
|
s.Metrics.AddFlags(fss.FlagSet("metrics"))
|
||||||
logsapi.AddFlags(s.Logs, fss.FlagSet("logs"))
|
logsapi.AddFlags(s.Logs, fss.FlagSet("logs"))
|
||||||
@@ -359,6 +364,9 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config, a
|
|||||||
if err := s.TTLAfterFinishedController.ApplyTo(&c.ComponentConfig.TTLAfterFinishedController); err != nil {
|
if err := s.TTLAfterFinishedController.ApplyTo(&c.ComponentConfig.TTLAfterFinishedController); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.ValidatingAdmissionPolicyStatusController.ApplyTo(&c.ComponentConfig.ValidatingAdmissionPolicyStatusController); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := s.SecureServing.ApplyTo(&c.SecureServing, &c.LoopbackClientConfig); err != nil {
|
if err := s.SecureServing.ApplyTo(&c.SecureServing, &c.LoopbackClientConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -63,6 +63,7 @@ import (
|
|||||||
serviceaccountconfig "k8s.io/kubernetes/pkg/controller/serviceaccount/config"
|
serviceaccountconfig "k8s.io/kubernetes/pkg/controller/serviceaccount/config"
|
||||||
statefulsetconfig "k8s.io/kubernetes/pkg/controller/statefulset/config"
|
statefulsetconfig "k8s.io/kubernetes/pkg/controller/statefulset/config"
|
||||||
ttlafterfinishedconfig "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config"
|
ttlafterfinishedconfig "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config"
|
||||||
|
validatingadmissionpolicystatusconfig "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config"
|
||||||
attachdetachconfig "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config"
|
attachdetachconfig "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config"
|
||||||
ephemeralvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config"
|
ephemeralvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config"
|
||||||
persistentvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config"
|
persistentvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config"
|
||||||
@@ -101,6 +102,7 @@ var args = []string{
|
|||||||
"--concurrent-service-syncs=2",
|
"--concurrent-service-syncs=2",
|
||||||
"--concurrent-serviceaccount-token-syncs=10",
|
"--concurrent-serviceaccount-token-syncs=10",
|
||||||
"--concurrent_rc_syncs=10",
|
"--concurrent_rc_syncs=10",
|
||||||
|
"--concurrent-validating-admission-policy-status-syncs=9",
|
||||||
"--configure-cloud-routes=false",
|
"--configure-cloud-routes=false",
|
||||||
"--contention-profiling=true",
|
"--contention-profiling=true",
|
||||||
"--controller-start-interval=2m",
|
"--controller-start-interval=2m",
|
||||||
@@ -409,6 +411,11 @@ func TestAddFlags(t *testing.T) {
|
|||||||
ConcurrentTTLSyncs: 8,
|
ConcurrentTTLSyncs: 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ValidatingAdmissionPolicyStatusController: &ValidatingAdmissionPolicyStatusControllerOptions{
|
||||||
|
&validatingadmissionpolicystatusconfig.ValidatingAdmissionPolicyStatusControllerConfiguration{
|
||||||
|
ConcurrentPolicySyncs: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
SecureServing: (&apiserveroptions.SecureServingOptions{
|
SecureServing: (&apiserveroptions.SecureServingOptions{
|
||||||
BindPort: 10001,
|
BindPort: 10001,
|
||||||
BindAddress: netutils.ParseIPSloppy("192.168.4.21"),
|
BindAddress: netutils.ParseIPSloppy("192.168.4.21"),
|
||||||
@@ -640,6 +647,9 @@ func TestApplyTo(t *testing.T) {
|
|||||||
TTLAfterFinishedController: ttlafterfinishedconfig.TTLAfterFinishedControllerConfiguration{
|
TTLAfterFinishedController: ttlafterfinishedconfig.TTLAfterFinishedControllerConfiguration{
|
||||||
ConcurrentTTLSyncs: 8,
|
ConcurrentTTLSyncs: 8,
|
||||||
},
|
},
|
||||||
|
ValidatingAdmissionPolicyStatusController: validatingadmissionpolicystatusconfig.ValidatingAdmissionPolicyStatusControllerConfiguration{
|
||||||
|
ConcurrentPolicySyncs: 9,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
validatingadmissionpolicystatusconfig "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidatingAdmissionPolicyStatusControllerOptions holds the ValidatingAdmissionPolicyStatusController options.
|
||||||
|
type ValidatingAdmissionPolicyStatusControllerOptions struct {
|
||||||
|
*validatingadmissionpolicystatusconfig.ValidatingAdmissionPolicyStatusControllerConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFlags adds flags related to ValidatingAdmissionPolicyStatusController for controller manager to the specified FlagSet.
|
||||||
|
func (o *ValidatingAdmissionPolicyStatusControllerOptions) AddFlags(fs *pflag.FlagSet) {
|
||||||
|
if o == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Int32Var(&o.ConcurrentPolicySyncs, "concurrent-validating-admission-policy-status-syncs", o.ConcurrentPolicySyncs, "The number of ValidatingAdmissionPolicyStatusController workers that are allowed to sync concurrently.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyTo fills up ValidatingAdmissionPolicyStatusController config with options.
|
||||||
|
func (o *ValidatingAdmissionPolicyStatusControllerOptions) ApplyTo(cfg *validatingadmissionpolicystatusconfig.ValidatingAdmissionPolicyStatusControllerConfiguration) error {
|
||||||
|
if o == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.ConcurrentPolicySyncs = o.ConcurrentPolicySyncs
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks validation of ValidatingAdmissionPolicyStatusControllerOptions.
|
||||||
|
func (o *ValidatingAdmissionPolicyStatusControllerOptions) Validate() []error {
|
||||||
|
if o == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var errs []error
|
||||||
|
if o.ConcurrentPolicySyncs <= 0 {
|
||||||
|
// omits controller or flag names because the CLI already includes these in the message.
|
||||||
|
errs = append(errs, fmt.Errorf("must be positive, got %d", o.ConcurrentPolicySyncs))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
pluginvalidatingadmissionpolicy "k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||||||
|
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"k8s.io/controller-manager/controller"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus"
|
||||||
|
"k8s.io/kubernetes/pkg/generated/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validatingAdmissionPolicyResource = admissionregistrationv1alpha1.SchemeGroupVersion.WithResource("validatingadmissionpolicies")
|
||||||
|
|
||||||
|
func startValidatingAdmissionPolicyStatusController(ctx context.Context, controllerContext ControllerContext) (controller.Interface, bool, error) {
|
||||||
|
// intended check against served resource but not feature gate.
|
||||||
|
// KCM won't start the controller without the feature gate set.
|
||||||
|
if !controllerContext.AvailableResources[validatingAdmissionPolicyResource] {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
typeChecker := &pluginvalidatingadmissionpolicy.TypeChecker{
|
||||||
|
SchemaResolver: resolver.NewDefinitionsSchemaResolver(scheme.Scheme, openapi.GetOpenAPIDefinitions),
|
||||||
|
RestMapper: controllerContext.RESTMapper,
|
||||||
|
}
|
||||||
|
c, err := validatingadmissionpolicystatus.NewController(
|
||||||
|
controllerContext.InformerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies(),
|
||||||
|
controllerContext.ClientBuilder.ClientOrDie("validatingadmissionpolicy-status-controller").AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies(),
|
||||||
|
typeChecker,
|
||||||
|
)
|
||||||
|
|
||||||
|
go c.Run(ctx, int(controllerContext.ComponentConfig.ValidatingAdmissionPolicyStatusController.ConcurrentPolicySyncs))
|
||||||
|
return nil, true, err
|
||||||
|
}
|
@@ -86,6 +86,7 @@ const (
|
|||||||
StorageVersionGarbageCollectorController = "storageversion-garbage-collector-controller"
|
StorageVersionGarbageCollectorController = "storageversion-garbage-collector-controller"
|
||||||
ResourceClaimController = "resourceclaim-controller"
|
ResourceClaimController = "resourceclaim-controller"
|
||||||
LegacyServiceAccountTokenCleanerController = "legacy-serviceaccount-token-cleaner-controller"
|
LegacyServiceAccountTokenCleanerController = "legacy-serviceaccount-token-cleaner-controller"
|
||||||
|
ValidatingAdmissionPolicyStatusController = "validatingadmissionpolicy-status-controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KCMControllerAliases returns a mapping of aliases to canonical controller names
|
// KCMControllerAliases returns a mapping of aliases to canonical controller names
|
||||||
|
@@ -41,6 +41,7 @@ import (
|
|||||||
serviceaccountconfig "k8s.io/kubernetes/pkg/controller/serviceaccount/config"
|
serviceaccountconfig "k8s.io/kubernetes/pkg/controller/serviceaccount/config"
|
||||||
statefulsetconfig "k8s.io/kubernetes/pkg/controller/statefulset/config"
|
statefulsetconfig "k8s.io/kubernetes/pkg/controller/statefulset/config"
|
||||||
ttlafterfinishedconfig "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config"
|
ttlafterfinishedconfig "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config"
|
||||||
|
validatingadmissionpolicystatusconfig "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config"
|
||||||
attachdetachconfig "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config"
|
attachdetachconfig "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config"
|
||||||
ephemeralvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config"
|
ephemeralvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config"
|
||||||
persistentvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config"
|
persistentvolumeconfig "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config"
|
||||||
@@ -132,6 +133,9 @@ type KubeControllerManagerConfiguration struct {
|
|||||||
// TTLAfterFinishedControllerConfiguration holds configuration for
|
// TTLAfterFinishedControllerConfiguration holds configuration for
|
||||||
// TTLAfterFinishedController related features.
|
// TTLAfterFinishedController related features.
|
||||||
TTLAfterFinishedController ttlafterfinishedconfig.TTLAfterFinishedControllerConfiguration
|
TTLAfterFinishedController ttlafterfinishedconfig.TTLAfterFinishedControllerConfiguration
|
||||||
|
// ValidatingAdmissionPolicyStatusControllerConfiguration holds configuration for
|
||||||
|
// ValidatingAdmissionPolicyStatusController related features.
|
||||||
|
ValidatingAdmissionPolicyStatusController validatingadmissionpolicystatusconfig.ValidatingAdmissionPolicyStatusControllerConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeprecatedControllerConfiguration contains elements be deprecated.
|
// DeprecatedControllerConfiguration contains elements be deprecated.
|
||||||
|
@@ -41,6 +41,7 @@ import (
|
|||||||
serviceaccountconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/serviceaccount/config/v1alpha1"
|
serviceaccountconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/serviceaccount/config/v1alpha1"
|
||||||
statefulsetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/statefulset/config/v1alpha1"
|
statefulsetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/statefulset/config/v1alpha1"
|
||||||
ttlafterfinishedconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config/v1alpha1"
|
ttlafterfinishedconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config/v1alpha1"
|
||||||
|
validatingadmissionpolicystatusv1alpha1 "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config/v1alpha1"
|
||||||
attachdetachconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config/v1alpha1"
|
attachdetachconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config/v1alpha1"
|
||||||
ephemeralvolumeconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config/v1alpha1"
|
ephemeralvolumeconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config/v1alpha1"
|
||||||
persistentvolumeconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config/v1alpha1"
|
persistentvolumeconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config/v1alpha1"
|
||||||
@@ -112,4 +113,6 @@ func SetDefaults_KubeControllerManagerConfiguration(obj *kubectrlmgrconfigv1alph
|
|||||||
ttlafterfinishedconfigv1alpha1.RecommendedDefaultTTLAfterFinishedControllerConfiguration(&obj.TTLAfterFinishedController)
|
ttlafterfinishedconfigv1alpha1.RecommendedDefaultTTLAfterFinishedControllerConfiguration(&obj.TTLAfterFinishedController)
|
||||||
// Use the default RecommendedDefaultPersistentVolumeBinderControllerConfiguration options
|
// Use the default RecommendedDefaultPersistentVolumeBinderControllerConfiguration options
|
||||||
persistentvolumeconfigv1alpha1.RecommendedDefaultPersistentVolumeBinderControllerConfiguration(&obj.PersistentVolumeBinderController)
|
persistentvolumeconfigv1alpha1.RecommendedDefaultPersistentVolumeBinderControllerConfiguration(&obj.PersistentVolumeBinderController)
|
||||||
|
// Use the default RecommendedDefaultValidatingAdmissionPolicyStatusControllerConfiguration options
|
||||||
|
validatingadmissionpolicystatusv1alpha1.RecommendedDefaultValidatingAdmissionPolicyStatusControllerConfiguration(&obj.ValidatingAdmissionPolicyStatusController)
|
||||||
}
|
}
|
||||||
|
@@ -50,6 +50,7 @@ import (
|
|||||||
serviceaccountconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/serviceaccount/config/v1alpha1"
|
serviceaccountconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/serviceaccount/config/v1alpha1"
|
||||||
statefulsetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/statefulset/config/v1alpha1"
|
statefulsetconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/statefulset/config/v1alpha1"
|
||||||
ttlafterfinishedconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config/v1alpha1"
|
ttlafterfinishedconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/ttlafterfinished/config/v1alpha1"
|
||||||
|
validatingadmissionpolicystatusconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config/v1alpha1"
|
||||||
attachdetachconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config/v1alpha1"
|
attachdetachconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/config/v1alpha1"
|
||||||
ephemeralconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config/v1alpha1"
|
ephemeralconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/ephemeral/config/v1alpha1"
|
||||||
persistentvolumeconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config/v1alpha1"
|
persistentvolumeconfigv1alpha1 "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/config/v1alpha1"
|
||||||
@@ -220,6 +221,9 @@ func autoConvert_v1alpha1_KubeControllerManagerConfiguration_To_config_KubeContr
|
|||||||
if err := ttlafterfinishedconfigv1alpha1.Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil {
|
if err := ttlafterfinishedconfigv1alpha1.Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := validatingadmissionpolicystatusconfigv1alpha1.Convert_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration_To_config_ValidatingAdmissionPolicyStatusControllerConfiguration(&in.ValidatingAdmissionPolicyStatusController, &out.ValidatingAdmissionPolicyStatusController, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +317,9 @@ func autoConvert_config_KubeControllerManagerConfiguration_To_v1alpha1_KubeContr
|
|||||||
if err := ttlafterfinishedconfigv1alpha1.Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil {
|
if err := ttlafterfinishedconfigv1alpha1.Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := validatingadmissionpolicystatusconfigv1alpha1.Convert_config_ValidatingAdmissionPolicyStatusControllerConfiguration_To_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(&in.ValidatingAdmissionPolicyStatusController, &out.ValidatingAdmissionPolicyStatusController, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -73,6 +73,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa
|
|||||||
out.SAController = in.SAController
|
out.SAController = in.SAController
|
||||||
out.ServiceController = in.ServiceController
|
out.ServiceController = in.ServiceController
|
||||||
out.TTLAfterFinishedController = in.TTLAfterFinishedController
|
out.TTLAfterFinishedController = in.TTLAfterFinishedController
|
||||||
|
out.ValidatingAdmissionPolicyStatusController = in.ValidatingAdmissionPolicyStatusController
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
pkg/controller/validatingadmissionpolicystatus/OWNERS
Normal file
6
pkg/controller/validatingadmissionpolicystatus/OWNERS
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
reviewers:
|
||||||
|
- jiahuif
|
||||||
|
labels:
|
||||||
|
- sig/api-machinery
|
19
pkg/controller/validatingadmissionpolicystatus/config/doc.go
Normal file
19
pkg/controller/validatingadmissionpolicystatus/config/doc.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
|
||||||
|
package config // import "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config"
|
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
// ValidatingAdmissionPolicyStatusControllerConfiguration contains elements describing ValidatingAdmissionPolicyStatusController.
|
||||||
|
type ValidatingAdmissionPolicyStatusControllerConfiguration struct {
|
||||||
|
// ConcurrentPolicySyncs is the number of policy objects that are
|
||||||
|
// allowed to sync concurrently. Larger number = quicker type checking,
|
||||||
|
// but more CPU (and network) load.
|
||||||
|
// The default value is 5.
|
||||||
|
ConcurrentPolicySyncs int32
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/conversion"
|
||||||
|
"k8s.io/kube-controller-manager/config/v1alpha1"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Important! The public back-and-forth conversion functions for the types in this package
|
||||||
|
// need to be manually exposed like this in order for other packages that reference
|
||||||
|
// this package to be able to call these conversion functions in an autogenerated manner.
|
||||||
|
// TODO: Fix the bug in conversion-gen so it automatically discovers these Convert_* functions
|
||||||
|
// in autogenerated code as well. This is a limitation that affects all controller configurations
|
||||||
|
// This issue was introduced in #72800
|
||||||
|
|
||||||
|
// Convert_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration_To_config_ValidatingAdmissionPolicyStatusControllerConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration_To_config_ValidatingAdmissionPolicyStatusControllerConfiguration(in *v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration, out *config.ValidatingAdmissionPolicyStatusControllerConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration_To_config_ValidatingAdmissionPolicyStatusControllerConfiguration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_config_ValidatingAdmissionPolicyStatusControllerConfiguration_To_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration is an autogenerated conversion function.
|
||||||
|
func Convert_config_ValidatingAdmissionPolicyStatusControllerConfiguration_To_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(in *config.ValidatingAdmissionPolicyStatusControllerConfiguration, out *v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration, s conversion.Scope) error {
|
||||||
|
return autoConvert_config_ValidatingAdmissionPolicyStatusControllerConfiguration_To_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(in, out, s)
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
kubectrlmgrconfigv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecommendedDefaultValidatingAdmissionPolicyStatusControllerConfiguration defaults a pointer to a
|
||||||
|
// ValidatingAdmissionPolicyStatusControllerConfiguration struct. This will set the recommended default
|
||||||
|
// values, but they may be subject to change between API versions. This function
|
||||||
|
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
|
||||||
|
// function to allow consumers of this type to set whatever defaults for their
|
||||||
|
// embedded configs. Forcing consumers to use these defaults would be problematic
|
||||||
|
// as defaulting in the scheme is done as part of the conversion, and there would
|
||||||
|
// be no easy way to opt-out. Instead, if you want to use this defaulting method
|
||||||
|
// run it in your wrapper struct of this type in its `SetDefaults_` method.
|
||||||
|
func RecommendedDefaultValidatingAdmissionPolicyStatusControllerConfiguration(obj *kubectrlmgrconfigv1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration) {
|
||||||
|
if obj.ConcurrentPolicySyncs == 0 {
|
||||||
|
obj.ConcurrentPolicySyncs = 5
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kubectrlmgrconfigv1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRecommendedDefaultValidatingAdmissionPolicyStatusControllerConfiguration(t *testing.T) {
|
||||||
|
config := new(kubectrlmgrconfigv1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration)
|
||||||
|
RecommendedDefaultValidatingAdmissionPolicyStatusControllerConfiguration(config)
|
||||||
|
if config.ConcurrentPolicySyncs != 5 {
|
||||||
|
t.Errorf("incorrect default value, expected 5 but got %v", config.ConcurrentPolicySyncs)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config
|
||||||
|
// +k8s:conversion-gen-external-types=k8s.io/kube-controller-manager/config/v1alpha1
|
||||||
|
|
||||||
|
package v1alpha1 // import "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config/v1alpha1"
|
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// SchemeBuilder is the scheme builder with scheme init functions to run for this API package
|
||||||
|
SchemeBuilder runtime.SchemeBuilder
|
||||||
|
// localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package,
|
||||||
|
// defaulting and conversion init funcs are registered as well.
|
||||||
|
localSchemeBuilder = &SchemeBuilder
|
||||||
|
// AddToScheme is a global function that registers this API group & version to a scheme
|
||||||
|
AddToScheme = localSchemeBuilder.AddToScheme
|
||||||
|
)
|
92
pkg/controller/validatingadmissionpolicystatus/config/v1alpha1/zz_generated.conversion.go
generated
Normal file
92
pkg/controller/validatingadmissionpolicystatus/config/v1alpha1/zz_generated.conversion.go
generated
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by conversion-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
v1alpha1 "k8s.io/kube-controller-manager/config/v1alpha1"
|
||||||
|
config "k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
localSchemeBuilder.Register(RegisterConversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterConversions adds conversion functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
func RegisterConversions(s *runtime.Scheme) error {
|
||||||
|
if err := s.AddGeneratedConversionFunc((*v1alpha1.GroupResource)(nil), (*v1.GroupResource)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_GroupResource_To_v1_GroupResource(a.(*v1alpha1.GroupResource), b.(*v1.GroupResource), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*v1.GroupResource)(nil), (*v1alpha1.GroupResource)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1_GroupResource_To_v1alpha1_GroupResource(a.(*v1.GroupResource), b.(*v1alpha1.GroupResource), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddConversionFunc((*config.ValidatingAdmissionPolicyStatusControllerConfiguration)(nil), (*v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_config_ValidatingAdmissionPolicyStatusControllerConfiguration_To_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(a.(*config.ValidatingAdmissionPolicyStatusControllerConfiguration), b.(*v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddConversionFunc((*v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration)(nil), (*config.ValidatingAdmissionPolicyStatusControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration_To_config_ValidatingAdmissionPolicyStatusControllerConfiguration(a.(*v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration), b.(*config.ValidatingAdmissionPolicyStatusControllerConfiguration), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_GroupResource_To_v1_GroupResource(in *v1alpha1.GroupResource, out *v1.GroupResource, s conversion.Scope) error {
|
||||||
|
out.Group = in.Group
|
||||||
|
out.Resource = in.Resource
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_GroupResource_To_v1_GroupResource is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_GroupResource_To_v1_GroupResource(in *v1alpha1.GroupResource, out *v1.GroupResource, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_GroupResource_To_v1_GroupResource(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1_GroupResource_To_v1alpha1_GroupResource(in *v1.GroupResource, out *v1alpha1.GroupResource, s conversion.Scope) error {
|
||||||
|
out.Group = in.Group
|
||||||
|
out.Resource = in.Resource
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1_GroupResource_To_v1alpha1_GroupResource is an autogenerated conversion function.
|
||||||
|
func Convert_v1_GroupResource_To_v1alpha1_GroupResource(in *v1.GroupResource, out *v1alpha1.GroupResource, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1_GroupResource_To_v1alpha1_GroupResource(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration_To_config_ValidatingAdmissionPolicyStatusControllerConfiguration(in *v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration, out *config.ValidatingAdmissionPolicyStatusControllerConfiguration, s conversion.Scope) error {
|
||||||
|
out.ConcurrentPolicySyncs = in.ConcurrentPolicySyncs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_config_ValidatingAdmissionPolicyStatusControllerConfiguration_To_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(in *config.ValidatingAdmissionPolicyStatusControllerConfiguration, out *v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration, s conversion.Scope) error {
|
||||||
|
out.ConcurrentPolicySyncs = in.ConcurrentPolicySyncs
|
||||||
|
return nil
|
||||||
|
}
|
22
pkg/controller/validatingadmissionpolicystatus/config/v1alpha1/zz_generated.deepcopy.go
generated
Normal file
22
pkg/controller/validatingadmissionpolicystatus/config/v1alpha1/zz_generated.deepcopy.go
generated
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
38
pkg/controller/validatingadmissionpolicystatus/config/zz_generated.deepcopy.go
generated
Normal file
38
pkg/controller/validatingadmissionpolicystatus/config/zz_generated.deepcopy.go
generated
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ValidatingAdmissionPolicyStatusControllerConfiguration) DeepCopyInto(out *ValidatingAdmissionPolicyStatusControllerConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingAdmissionPolicyStatusControllerConfiguration.
|
||||||
|
func (in *ValidatingAdmissionPolicyStatusControllerConfiguration) DeepCopy() *ValidatingAdmissionPolicyStatusControllerConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ValidatingAdmissionPolicyStatusControllerConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
162
pkg/controller/validatingadmissionpolicystatus/controller.go
Normal file
162
pkg/controller/validatingadmissionpolicystatus/controller.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package validatingadmissionpolicystatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||||||
|
admissionregistrationv1alpha1apply "k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1"
|
||||||
|
informerv1alpha1 "k8s.io/client-go/informers/admissionregistration/v1alpha1"
|
||||||
|
admissionregistrationv1alpha1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControllerName has "Status" in it to differentiate this controller with the other that runs in API server.
|
||||||
|
const ControllerName = "validatingadmissionpolicy-status"
|
||||||
|
|
||||||
|
// Controller is the ValidatingAdmissionPolicy Status controller that reconciles the Status field of each policy object.
|
||||||
|
// This controller runs type checks against referred types for each policy definition.
|
||||||
|
type Controller struct {
|
||||||
|
policyInformer informerv1alpha1.ValidatingAdmissionPolicyInformer
|
||||||
|
policyQueue workqueue.RateLimitingInterface
|
||||||
|
policySynced cache.InformerSynced
|
||||||
|
policyClient admissionregistrationv1alpha1.ValidatingAdmissionPolicyInterface
|
||||||
|
|
||||||
|
// typeChecker checks the policy's expressions for type errors.
|
||||||
|
// Type of params is defined in policy.Spec.ParamsKind
|
||||||
|
// Types of object are calculated from policy.Spec.MatchingConstraints
|
||||||
|
typeChecker *validatingadmissionpolicy.TypeChecker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Run(ctx context.Context, workers int) {
|
||||||
|
defer utilruntime.HandleCrash()
|
||||||
|
|
||||||
|
if !cache.WaitForNamedCacheSync(ControllerName, ctx.Done(), c.policySynced) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer c.policyQueue.ShutDown()
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
go wait.UntilWithContext(ctx, c.runWorker, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(policyInformer informerv1alpha1.ValidatingAdmissionPolicyInformer, policyClient admissionregistrationv1alpha1.ValidatingAdmissionPolicyInterface, typeChecker *validatingadmissionpolicy.TypeChecker) (*Controller, error) {
|
||||||
|
c := &Controller{
|
||||||
|
policyInformer: policyInformer,
|
||||||
|
policyQueue: workqueue.NewRateLimitingQueueWithConfig(workqueue.DefaultControllerRateLimiter(), workqueue.RateLimitingQueueConfig{Name: ControllerName}),
|
||||||
|
policyClient: policyClient,
|
||||||
|
typeChecker: typeChecker,
|
||||||
|
}
|
||||||
|
reg, err := policyInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(obj interface{}) {
|
||||||
|
c.enqueuePolicy(obj)
|
||||||
|
},
|
||||||
|
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||||
|
c.enqueuePolicy(newObj)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.policySynced = reg.HasSynced
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) enqueuePolicy(policy any) {
|
||||||
|
if policy, ok := policy.(*v1alpha1.ValidatingAdmissionPolicy); ok {
|
||||||
|
// policy objects are cluster-scoped, no point include its namespace.
|
||||||
|
key := policy.ObjectMeta.Name
|
||||||
|
if key == "" {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("cannot get name of object %v", policy))
|
||||||
|
}
|
||||||
|
c.policyQueue.Add(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) runWorker(ctx context.Context) {
|
||||||
|
for c.processNextWorkItem(ctx) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) processNextWorkItem(ctx context.Context) bool {
|
||||||
|
key, shutdown := c.policyQueue.Get()
|
||||||
|
if shutdown {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer c.policyQueue.Done(key)
|
||||||
|
|
||||||
|
err := func() error {
|
||||||
|
key, ok := key.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expect a string but got %v", key)
|
||||||
|
}
|
||||||
|
policy, err := c.policyInformer.Lister().Get(key)
|
||||||
|
if err != nil {
|
||||||
|
if kerrors.IsNotFound(err) {
|
||||||
|
// If not found, the policy is being deleting, do nothing.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.reconcile(ctx, policy)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
c.policyQueue.Forget(key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
utilruntime.HandleError(err)
|
||||||
|
c.policyQueue.AddRateLimited(key)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) reconcile(ctx context.Context, policy *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||||
|
if policy == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if policy.Generation <= policy.Status.ObservedGeneration {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
warnings := c.typeChecker.Check(policy)
|
||||||
|
warningsConfig := make([]*admissionregistrationv1alpha1apply.ExpressionWarningApplyConfiguration, 0, len(warnings))
|
||||||
|
for _, warning := range warnings {
|
||||||
|
warningsConfig = append(warningsConfig, admissionregistrationv1alpha1apply.ExpressionWarning().
|
||||||
|
WithFieldRef(warning.FieldRef).
|
||||||
|
WithWarning(warning.Warning))
|
||||||
|
}
|
||||||
|
applyConfig := admissionregistrationv1alpha1apply.ValidatingAdmissionPolicy(policy.Name).
|
||||||
|
WithStatus(admissionregistrationv1alpha1apply.ValidatingAdmissionPolicyStatus().
|
||||||
|
WithObservedGeneration(policy.Generation).
|
||||||
|
WithTypeChecking(admissionregistrationv1alpha1apply.TypeChecking().
|
||||||
|
WithExpressionWarnings(warningsConfig...)))
|
||||||
|
_, err := c.policyClient.ApplyStatus(ctx, applyConfig, metav1.ApplyOptions{FieldManager: ControllerName, Force: true})
|
||||||
|
return err
|
||||||
|
}
|
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package validatingadmissionpolicystatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||||||
|
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"k8s.io/kubernetes/pkg/generated/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTypeChecking(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy
|
||||||
|
assertFieldRef func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) // warning.fieldRef
|
||||||
|
assertWarnings func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) // warning.warning
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "deployment with correct expression",
|
||||||
|
policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.spec.replicas > 1",
|
||||||
|
},
|
||||||
|
}, makePolicy("replicated-deployment"))),
|
||||||
|
assertFieldRef: toHaveLengthOf(0),
|
||||||
|
assertWarnings: toHaveLengthOf(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deployment with type confusion",
|
||||||
|
policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.spec.replicas < 100", // this one passes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "object.spec.replicas > '1'", // '1' should be int
|
||||||
|
},
|
||||||
|
}, makePolicy("confused-deployment"))),
|
||||||
|
assertFieldRef: toBe("spec.validations[1].expression"),
|
||||||
|
assertWarnings: toHaveSubstring(`found no matching overload for '_>_' applied to '(int, string)'`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two expressions different type checking errors",
|
||||||
|
policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.spec.nonExistingFirst > 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "object.spec.replicas > '1'", // '1' should be int
|
||||||
|
},
|
||||||
|
}, makePolicy("confused-deployment"))),
|
||||||
|
assertFieldRef: toBe("spec.validations[0].expression", "spec.validations[1].expression"),
|
||||||
|
assertWarnings: toHaveSubstring(
|
||||||
|
"undefined field 'nonExistingFirst'",
|
||||||
|
`found no matching overload for '_>_' applied to '(int, string)'`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one expression, two warnings",
|
||||||
|
policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.spec.replicas < 100", // this one passes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "object.spec.replicas > '1' && object.spec.nonExisting == 1",
|
||||||
|
},
|
||||||
|
}, makePolicy("confused-deployment"))),
|
||||||
|
assertFieldRef: toBe("spec.validations[1].expression"),
|
||||||
|
assertWarnings: toHaveMultipleSubstrings([]string{"undefined field 'nonExisting'", `found no matching overload for '_>_' applied to '(int, string)'`}),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
policy := tc.policy.DeepCopy()
|
||||||
|
policy.ObjectMeta.Generation = 1 // fake storage does not do this automatically
|
||||||
|
client := fake.NewSimpleClientset(policy)
|
||||||
|
informerFactory := informers.NewSharedInformerFactory(client, 0)
|
||||||
|
typeChecker := &validatingadmissionpolicy.TypeChecker{
|
||||||
|
SchemaResolver: resolver.NewDefinitionsSchemaResolver(scheme.Scheme, openapi.GetOpenAPIDefinitions),
|
||||||
|
RestMapper: testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme),
|
||||||
|
}
|
||||||
|
controller, err := NewController(
|
||||||
|
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies(),
|
||||||
|
client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies(),
|
||||||
|
typeChecker,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create controller: %v", err)
|
||||||
|
}
|
||||||
|
go informerFactory.Start(ctx.Done())
|
||||||
|
go controller.Run(ctx, 1)
|
||||||
|
err = wait.PollUntilContextCancel(ctx, time.Second, false, func(ctx context.Context) (done bool, err error) {
|
||||||
|
name := policy.Name
|
||||||
|
// wait until the typeChecking is set, which means the type checking
|
||||||
|
// is complete.
|
||||||
|
updated, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Get(ctx, name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if updated.Status.TypeChecking != nil {
|
||||||
|
policy = updated
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tc.assertFieldRef(policy.Status.TypeChecking.ExpressionWarnings, t)
|
||||||
|
tc.assertWarnings(policy.Status.TypeChecking.ExpressionWarnings, t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to initialize controller: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBe(expected ...string) func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
return func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
if len(expected) != len(warnings) {
|
||||||
|
t.Fatalf("mismatched length, expect %d, got %d", len(expected), len(warnings))
|
||||||
|
}
|
||||||
|
for i := range expected {
|
||||||
|
if expected[i] != warnings[i].FieldRef {
|
||||||
|
t.Errorf("expected %q but got %q", expected[i], warnings[i].FieldRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toHaveSubstring(substrings ...string) func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
return func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
if len(substrings) != len(warnings) {
|
||||||
|
t.Fatalf("mismatched length, expect %d, got %d", len(substrings), len(warnings))
|
||||||
|
}
|
||||||
|
for i := range substrings {
|
||||||
|
if !strings.Contains(warnings[i].Warning, substrings[i]) {
|
||||||
|
t.Errorf("missing expected substring %q in %v", substrings[i], warnings[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toHaveMultipleSubstrings(substrings ...[]string) func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
return func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
if len(substrings) != len(warnings) {
|
||||||
|
t.Fatalf("mismatched length, expect %d, got %d", len(substrings), len(warnings))
|
||||||
|
}
|
||||||
|
for i, expectedSubstrings := range substrings {
|
||||||
|
for _, s := range expectedSubstrings {
|
||||||
|
if !strings.Contains(warnings[i].Warning, s) {
|
||||||
|
t.Errorf("missing expected substring %q in %v", substrings[i], warnings[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toHaveLengthOf(n int) func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
return func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
||||||
|
if n != len(warnings) {
|
||||||
|
t.Fatalf("mismatched length, expect %d, got %d", n, len(warnings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withGVRMatch(groups []string, versions []string, resources []string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{
|
||||||
|
ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{
|
||||||
|
{
|
||||||
|
RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{
|
||||||
|
Operations: []admissionregistrationv1.OperationType{
|
||||||
|
"*",
|
||||||
|
},
|
||||||
|
Rule: admissionregistrationv1.Rule{
|
||||||
|
APIGroups: groups,
|
||||||
|
APIVersions: versions,
|
||||||
|
Resources: resources,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func withValidations(validations []admissionregistrationv1alpha1.Validation, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
policy.Spec.Validations = validations
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePolicy(name string) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicy{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||||
|
}
|
||||||
|
}
|
34
pkg/generated/openapi/zz_generated.openapi.go
generated
34
pkg/generated/openapi/zz_generated.openapi.go
generated
@@ -1078,6 +1078,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||||||
"k8s.io/kube-controller-manager/config/v1alpha1.SAControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_SAControllerConfiguration(ref),
|
"k8s.io/kube-controller-manager/config/v1alpha1.SAControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_SAControllerConfiguration(ref),
|
||||||
"k8s.io/kube-controller-manager/config/v1alpha1.StatefulSetControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_StatefulSetControllerConfiguration(ref),
|
"k8s.io/kube-controller-manager/config/v1alpha1.StatefulSetControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_StatefulSetControllerConfiguration(ref),
|
||||||
"k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_TTLAfterFinishedControllerConfiguration(ref),
|
"k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_TTLAfterFinishedControllerConfiguration(ref),
|
||||||
|
"k8s.io/kube-controller-manager/config/v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(ref),
|
||||||
"k8s.io/kube-controller-manager/config/v1alpha1.VolumeConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_VolumeConfiguration(ref),
|
"k8s.io/kube-controller-manager/config/v1alpha1.VolumeConfiguration": schema_k8sio_kube_controller_manager_config_v1alpha1_VolumeConfiguration(ref),
|
||||||
"k8s.io/kube-proxy/config/v1alpha1.DetectLocalConfiguration": schema_k8sio_kube_proxy_config_v1alpha1_DetectLocalConfiguration(ref),
|
"k8s.io/kube-proxy/config/v1alpha1.DetectLocalConfiguration": schema_k8sio_kube_proxy_config_v1alpha1_DetectLocalConfiguration(ref),
|
||||||
"k8s.io/kube-proxy/config/v1alpha1.KubeProxyConfiguration": schema_k8sio_kube_proxy_config_v1alpha1_KubeProxyConfiguration(ref),
|
"k8s.io/kube-proxy/config/v1alpha1.KubeProxyConfiguration": schema_k8sio_kube_proxy_config_v1alpha1_KubeProxyConfiguration(ref),
|
||||||
@@ -53218,12 +53219,19 @@ func schema_k8sio_kube_controller_manager_config_v1alpha1_KubeControllerManagerC
|
|||||||
Ref: ref("k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration"),
|
Ref: ref("k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"ValidatingAdmissionPolicyStatusController": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "ValidatingAdmissionPolicyStatusControllerConfiguration holds configuration for ValidatingAdmissionPolicyStatusController related features.",
|
||||||
|
Default: map[string]interface{}{},
|
||||||
|
Ref: ref("k8s.io/kube-controller-manager/config/v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration"),
|
||||||
},
|
},
|
||||||
Required: []string{"Generic", "KubeCloudShared", "AttachDetachController", "CSRSigningController", "DaemonSetController", "DeploymentController", "StatefulSetController", "DeprecatedController", "EndpointController", "EndpointSliceController", "EndpointSliceMirroringController", "EphemeralVolumeController", "GarbageCollectorController", "HPAController", "JobController", "CronJobController", "LegacySATokenCleaner", "NamespaceController", "NodeIPAMController", "NodeLifecycleController", "PersistentVolumeBinderController", "PodGCController", "ReplicaSetController", "ReplicationController", "ResourceQuotaController", "SAController", "ServiceController", "TTLAfterFinishedController"},
|
},
|
||||||
|
},
|
||||||
|
Required: []string{"Generic", "KubeCloudShared", "AttachDetachController", "CSRSigningController", "DaemonSetController", "DeploymentController", "StatefulSetController", "DeprecatedController", "EndpointController", "EndpointSliceController", "EndpointSliceMirroringController", "EphemeralVolumeController", "GarbageCollectorController", "HPAController", "JobController", "CronJobController", "LegacySATokenCleaner", "NamespaceController", "NodeIPAMController", "NodeLifecycleController", "PersistentVolumeBinderController", "PodGCController", "ReplicaSetController", "ReplicationController", "ResourceQuotaController", "SAController", "ServiceController", "TTLAfterFinishedController", "ValidatingAdmissionPolicyStatusController"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
"k8s.io/cloud-provider/config/v1alpha1.KubeCloudSharedConfiguration", "k8s.io/cloud-provider/controllers/service/config/v1alpha1.ServiceControllerConfiguration", "k8s.io/controller-manager/config/v1alpha1.GenericControllerManagerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.AttachDetachControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CSRSigningControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CronJobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DaemonSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeploymentControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeprecatedControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceMirroringControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EphemeralVolumeControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.GarbageCollectorControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.HPAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.JobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.LegacySATokenCleanerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NamespaceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeIPAMControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeLifecycleControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PersistentVolumeBinderControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PodGCControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicaSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicationControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ResourceQuotaControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.SAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.StatefulSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration"},
|
"k8s.io/cloud-provider/config/v1alpha1.KubeCloudSharedConfiguration", "k8s.io/cloud-provider/controllers/service/config/v1alpha1.ServiceControllerConfiguration", "k8s.io/controller-manager/config/v1alpha1.GenericControllerManagerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.AttachDetachControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CSRSigningControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.CronJobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DaemonSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeploymentControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.DeprecatedControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EndpointSliceMirroringControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.EphemeralVolumeControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.GarbageCollectorControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.HPAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.JobControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.LegacySATokenCleanerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NamespaceControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeIPAMControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.NodeLifecycleControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PersistentVolumeBinderControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.PodGCControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicaSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ReplicationControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ResourceQuotaControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.SAControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.StatefulSetControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.TTLAfterFinishedControllerConfiguration", "k8s.io/kube-controller-manager/config/v1alpha1.ValidatingAdmissionPolicyStatusControllerConfiguration"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53705,6 +53713,28 @@ func schema_k8sio_kube_controller_manager_config_v1alpha1_TTLAfterFinishedContro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func schema_k8sio_kube_controller_manager_config_v1alpha1_ValidatingAdmissionPolicyStatusControllerConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "ValidatingAdmissionPolicyStatusControllerConfiguration contains elements describing ValidatingAdmissionPolicyStatusController.",
|
||||||
|
Type: []string{"object"},
|
||||||
|
Properties: map[string]spec.Schema{
|
||||||
|
"ConcurrentPolicySyncs": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "ConcurrentPolicySyncs is the number of policy objects that are allowed to sync concurrently. Larger number = quicker type checking, but more CPU (and network) load. The default value is 5.",
|
||||||
|
Default: 0,
|
||||||
|
Type: []string{"integer"},
|
||||||
|
Format: "int32",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: []string{"ConcurrentPolicySyncs"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func schema_k8sio_kube_controller_manager_config_v1alpha1_VolumeConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
func schema_k8sio_kube_controller_manager_config_v1alpha1_VolumeConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
return common.OpenAPIDefinition{
|
return common.OpenAPIDefinition{
|
||||||
Schema: spec.Schema{
|
Schema: spec.Schema{
|
||||||
|
@@ -28,7 +28,6 @@ import (
|
|||||||
utilwait "k8s.io/apimachinery/pkg/util/wait"
|
utilwait "k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
webhookinit "k8s.io/apiserver/pkg/admission/plugin/webhook/initializer"
|
webhookinit "k8s.io/apiserver/pkg/admission/plugin/webhook/initializer"
|
||||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
egressselector "k8s.io/apiserver/pkg/server/egressselector"
|
egressselector "k8s.io/apiserver/pkg/server/egressselector"
|
||||||
"k8s.io/apiserver/pkg/util/webhook"
|
"k8s.io/apiserver/pkg/util/webhook"
|
||||||
@@ -48,7 +47,7 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New sets up the plugins and admission start hooks needed for admission
|
// New sets up the plugins and admission start hooks needed for admission
|
||||||
func (c *Config) New(proxyTransport *http.Transport, egressSelector *egressselector.EgressSelector, serviceResolver webhook.ServiceResolver, tp trace.TracerProvider, schemaResolver resolver.SchemaResolver) ([]admission.PluginInitializer, genericapiserver.PostStartHookFunc, error) {
|
func (c *Config) New(proxyTransport *http.Transport, egressSelector *egressselector.EgressSelector, serviceResolver webhook.ServiceResolver, tp trace.TracerProvider) ([]admission.PluginInitializer, genericapiserver.PostStartHookFunc, error) {
|
||||||
webhookAuthResolverWrapper := webhook.NewDefaultAuthenticationInfoResolverWrapper(proxyTransport, egressSelector, c.LoopbackClientConfig, tp)
|
webhookAuthResolverWrapper := webhook.NewDefaultAuthenticationInfoResolverWrapper(proxyTransport, egressSelector, c.LoopbackClientConfig, tp)
|
||||||
webhookPluginInitializer := webhookinit.NewPluginInitializer(webhookAuthResolverWrapper, serviceResolver)
|
webhookPluginInitializer := webhookinit.NewPluginInitializer(webhookAuthResolverWrapper, serviceResolver)
|
||||||
|
|
||||||
@@ -70,7 +69,6 @@ func (c *Config) New(proxyTransport *http.Transport, egressSelector *egressselec
|
|||||||
cloudConfig,
|
cloudConfig,
|
||||||
discoveryRESTMapper,
|
discoveryRESTMapper,
|
||||||
quotainstall.NewQuotaConfigurationForAdmission(),
|
quotainstall.NewQuotaConfigurationForAdmission(),
|
||||||
schemaResolver,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
admissionPostStartHook := func(context genericapiserver.PostStartHookContext) error {
|
admissionPostStartHook := func(context genericapiserver.PostStartHookContext) error {
|
||||||
|
@@ -20,7 +20,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/initializer"
|
"k8s.io/apiserver/pkg/admission/initializer"
|
||||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
|
||||||
quota "k8s.io/apiserver/pkg/quota/v1"
|
quota "k8s.io/apiserver/pkg/quota/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@ type PluginInitializer struct {
|
|||||||
cloudConfig []byte
|
cloudConfig []byte
|
||||||
restMapper meta.RESTMapper
|
restMapper meta.RESTMapper
|
||||||
quotaConfiguration quota.Configuration
|
quotaConfiguration quota.Configuration
|
||||||
schemaResolver resolver.SchemaResolver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ admission.PluginInitializer = &PluginInitializer{}
|
var _ admission.PluginInitializer = &PluginInitializer{}
|
||||||
@@ -48,13 +46,11 @@ func NewPluginInitializer(
|
|||||||
cloudConfig []byte,
|
cloudConfig []byte,
|
||||||
restMapper meta.RESTMapper,
|
restMapper meta.RESTMapper,
|
||||||
quotaConfiguration quota.Configuration,
|
quotaConfiguration quota.Configuration,
|
||||||
schemaResolver resolver.SchemaResolver,
|
|
||||||
) *PluginInitializer {
|
) *PluginInitializer {
|
||||||
return &PluginInitializer{
|
return &PluginInitializer{
|
||||||
cloudConfig: cloudConfig,
|
cloudConfig: cloudConfig,
|
||||||
restMapper: restMapper,
|
restMapper: restMapper,
|
||||||
quotaConfiguration: quotaConfiguration,
|
quotaConfiguration: quotaConfiguration,
|
||||||
schemaResolver: schemaResolver,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +68,4 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
|
|||||||
if wants, ok := plugin.(initializer.WantsQuotaConfiguration); ok {
|
if wants, ok := plugin.(initializer.WantsQuotaConfiguration); ok {
|
||||||
wants.SetQuotaConfiguration(i.quotaConfiguration)
|
wants.SetQuotaConfiguration(i.quotaConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
if wants, ok := plugin.(initializer.WantsSchemaResolver); ok {
|
|
||||||
wants.SetSchemaResolver(i.schemaResolver)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -49,7 +49,7 @@ func (p *WantsCloudConfigAdmissionPlugin) SetCloudConfig(cloudConfig []byte) {
|
|||||||
|
|
||||||
func TestCloudConfigAdmissionPlugin(t *testing.T) {
|
func TestCloudConfigAdmissionPlugin(t *testing.T) {
|
||||||
cloudConfig := []byte("cloud-configuration")
|
cloudConfig := []byte("cloud-configuration")
|
||||||
initializer := NewPluginInitializer(cloudConfig, nil, nil, nil)
|
initializer := NewPluginInitializer(cloudConfig, nil, nil)
|
||||||
wantsCloudConfigAdmission := &WantsCloudConfigAdmissionPlugin{}
|
wantsCloudConfigAdmission := &WantsCloudConfigAdmissionPlugin{}
|
||||||
initializer.Initialize(wantsCloudConfigAdmission)
|
initializer.Initialize(wantsCloudConfigAdmission)
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ func (p *WantsRESTMapperAdmissionPlugin) SetRESTMapper(mapper meta.RESTMapper) {
|
|||||||
|
|
||||||
func TestRESTMapperAdmissionPlugin(t *testing.T) {
|
func TestRESTMapperAdmissionPlugin(t *testing.T) {
|
||||||
mapper := doNothingRESTMapper{}
|
mapper := doNothingRESTMapper{}
|
||||||
initializer := NewPluginInitializer(nil, mapper, nil, nil)
|
initializer := NewPluginInitializer(nil, mapper, nil)
|
||||||
wantsRESTMapperAdmission := &WantsRESTMapperAdmissionPlugin{}
|
wantsRESTMapperAdmission := &WantsRESTMapperAdmissionPlugin{}
|
||||||
initializer.Initialize(wantsRESTMapperAdmission)
|
initializer.Initialize(wantsRESTMapperAdmission)
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ func (p *WantsQuotaConfigurationAdmissionPlugin) SetQuotaConfiguration(config qu
|
|||||||
|
|
||||||
func TestQuotaConfigurationAdmissionPlugin(t *testing.T) {
|
func TestQuotaConfigurationAdmissionPlugin(t *testing.T) {
|
||||||
config := doNothingQuotaConfiguration{}
|
config := doNothingQuotaConfiguration{}
|
||||||
initializer := NewPluginInitializer(nil, nil, config, nil)
|
initializer := NewPluginInitializer(nil, nil, config)
|
||||||
wantsQuotaConfigurationAdmission := &WantsQuotaConfigurationAdmissionPlugin{}
|
wantsQuotaConfigurationAdmission := &WantsQuotaConfigurationAdmissionPlugin{}
|
||||||
initializer.Initialize(wantsQuotaConfigurationAdmission)
|
initializer.Initialize(wantsQuotaConfigurationAdmission)
|
||||||
|
|
||||||
|
@@ -139,7 +139,7 @@ func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) {
|
|||||||
return nil, fmt.Errorf("unexpected error while constructing resource list from fake discovery client: %v", err)
|
return nil, fmt.Errorf("unexpected error while constructing resource list from fake discovery client: %v", err)
|
||||||
}
|
}
|
||||||
restMapper := restmapper.NewDiscoveryRESTMapper(restMapperRes)
|
restMapper := restmapper.NewDiscoveryRESTMapper(restMapperRes)
|
||||||
pluginInitializer := kubeadmission.NewPluginInitializer(nil, restMapper, nil, nil)
|
pluginInitializer := kubeadmission.NewPluginInitializer(nil, restMapper, nil)
|
||||||
initializersChain := admission.PluginInitializers{}
|
initializersChain := admission.PluginInitializers{}
|
||||||
initializersChain = append(initializersChain, genericPluginInitializer)
|
initializersChain = append(initializersChain, genericPluginInitializer)
|
||||||
initializersChain = append(initializersChain, pluginInitializer)
|
initializersChain = append(initializersChain, pluginInitializer)
|
||||||
|
@@ -111,7 +111,7 @@ func createHandlerWithConfig(kubeClient kubernetes.Interface, informerFactory in
|
|||||||
|
|
||||||
initializers := admission.PluginInitializers{
|
initializers := admission.PluginInitializers{
|
||||||
genericadmissioninitializer.New(kubeClient, nil, informerFactory, nil, nil, stopCh),
|
genericadmissioninitializer.New(kubeClient, nil, informerFactory, nil, nil, stopCh),
|
||||||
kubeapiserveradmission.NewPluginInitializer(nil, nil, quotaConfiguration, nil),
|
kubeapiserveradmission.NewPluginInitializer(nil, nil, quotaConfiguration),
|
||||||
}
|
}
|
||||||
initializers.Initialize(handler)
|
initializers.Initialize(handler)
|
||||||
|
|
||||||
|
@@ -441,6 +441,18 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
|
|||||||
eventsRule(),
|
eventsRule(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.ValidatingAdmissionPolicy) {
|
||||||
|
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "validatingadmissionpolicy-status-controller"},
|
||||||
|
Rules: []rbacv1.PolicyRule{
|
||||||
|
rbacv1helpers.NewRule("get", "list", "watch").Groups(admissionRegistrationGroup).
|
||||||
|
Resources("validatingadmissionpolicies").RuleOrDie(),
|
||||||
|
rbacv1helpers.NewRule("get", "patch", "update").Groups(admissionRegistrationGroup).
|
||||||
|
Resources("validatingadmissionpolicies/status").RuleOrDie(),
|
||||||
|
eventsRule(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) &&
|
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) &&
|
||||||
utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) {
|
utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) {
|
||||||
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
|
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
|
||||||
|
@@ -62,6 +62,7 @@ const (
|
|||||||
networkingGroup = "networking.k8s.io"
|
networkingGroup = "networking.k8s.io"
|
||||||
eventsGroup = "events.k8s.io"
|
eventsGroup = "events.k8s.io"
|
||||||
internalAPIServerGroup = "internal.apiserver.k8s.io"
|
internalAPIServerGroup = "internal.apiserver.k8s.io"
|
||||||
|
admissionRegistrationGroup = "admissionregistration.k8s.io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addDefaultMetadata(obj runtime.Object) {
|
func addDefaultMetadata(obj runtime.Object) {
|
||||||
|
@@ -24,7 +24,6 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/component-base/featuregate"
|
"k8s.io/component-base/featuregate"
|
||||||
@@ -74,7 +73,6 @@ type celAdmissionPlugin struct {
|
|||||||
dynamicClient dynamic.Interface
|
dynamicClient dynamic.Interface
|
||||||
stopCh <-chan struct{}
|
stopCh <-chan struct{}
|
||||||
authorizer authorizer.Authorizer
|
authorizer authorizer.Authorizer
|
||||||
schemaResolver resolver.SchemaResolver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ initializer.WantsExternalKubeInformerFactory = &celAdmissionPlugin{}
|
var _ initializer.WantsExternalKubeInformerFactory = &celAdmissionPlugin{}
|
||||||
@@ -83,7 +81,6 @@ var _ initializer.WantsRESTMapper = &celAdmissionPlugin{}
|
|||||||
var _ initializer.WantsDynamicClient = &celAdmissionPlugin{}
|
var _ initializer.WantsDynamicClient = &celAdmissionPlugin{}
|
||||||
var _ initializer.WantsDrainedNotification = &celAdmissionPlugin{}
|
var _ initializer.WantsDrainedNotification = &celAdmissionPlugin{}
|
||||||
var _ initializer.WantsAuthorizer = &celAdmissionPlugin{}
|
var _ initializer.WantsAuthorizer = &celAdmissionPlugin{}
|
||||||
var _ initializer.WantsSchemaResolver = &celAdmissionPlugin{}
|
|
||||||
var _ admission.InitializationValidator = &celAdmissionPlugin{}
|
var _ admission.InitializationValidator = &celAdmissionPlugin{}
|
||||||
var _ admission.ValidationInterface = &celAdmissionPlugin{}
|
var _ admission.ValidationInterface = &celAdmissionPlugin{}
|
||||||
|
|
||||||
@@ -116,11 +113,6 @@ func (c *celAdmissionPlugin) SetDrainedNotification(stopCh <-chan struct{}) {
|
|||||||
func (c *celAdmissionPlugin) SetAuthorizer(authorizer authorizer.Authorizer) {
|
func (c *celAdmissionPlugin) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||||
c.authorizer = authorizer
|
c.authorizer = authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *celAdmissionPlugin) SetSchemaResolver(resolver resolver.SchemaResolver) {
|
|
||||||
c.schemaResolver = resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *celAdmissionPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
func (c *celAdmissionPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||||
if featureGates.Enabled(features.ValidatingAdmissionPolicy) {
|
if featureGates.Enabled(features.ValidatingAdmissionPolicy) {
|
||||||
c.enabled = true
|
c.enabled = true
|
||||||
@@ -154,7 +146,7 @@ func (c *celAdmissionPlugin) ValidateInitialization() error {
|
|||||||
if c.authorizer == nil {
|
if c.authorizer == nil {
|
||||||
return errors.New("missing authorizer")
|
return errors.New("missing authorizer")
|
||||||
}
|
}
|
||||||
c.evaluator = NewAdmissionController(c.informerFactory, c.client, c.restMapper, c.schemaResolver /* (optional) */, c.dynamicClient, c.authorizer)
|
c.evaluator = NewAdmissionController(c.informerFactory, c.client, c.restMapper, c.dynamicClient, c.authorizer)
|
||||||
if err := c.evaluator.ValidateInitialization(); err != nil {
|
if err := c.evaluator.ValidateInitialization(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -25,8 +25,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
"k8s.io/api/admissionregistration/v1alpha1"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
@@ -44,12 +42,12 @@ import (
|
|||||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
|
||||||
"k8s.io/apiserver/pkg/warning"
|
"k8s.io/apiserver/pkg/warning"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ CELPolicyEvaluator = &celAdmissionController{}
|
var _ CELPolicyEvaluator = &celAdmissionController{}
|
||||||
@@ -128,21 +126,15 @@ func NewAdmissionController(
|
|||||||
informerFactory informers.SharedInformerFactory,
|
informerFactory informers.SharedInformerFactory,
|
||||||
client kubernetes.Interface,
|
client kubernetes.Interface,
|
||||||
restMapper meta.RESTMapper,
|
restMapper meta.RESTMapper,
|
||||||
schemaResolver resolver.SchemaResolver,
|
|
||||||
dynamicClient dynamic.Interface,
|
dynamicClient dynamic.Interface,
|
||||||
authz authorizer.Authorizer,
|
authz authorizer.Authorizer,
|
||||||
) CELPolicyEvaluator {
|
) CELPolicyEvaluator {
|
||||||
var typeChecker *TypeChecker
|
|
||||||
if schemaResolver != nil {
|
|
||||||
typeChecker = &TypeChecker{schemaResolver: schemaResolver, restMapper: restMapper}
|
|
||||||
}
|
|
||||||
return &celAdmissionController{
|
return &celAdmissionController{
|
||||||
definitions: atomic.Value{},
|
definitions: atomic.Value{},
|
||||||
policyController: newPolicyController(
|
policyController: newPolicyController(
|
||||||
restMapper,
|
restMapper,
|
||||||
client,
|
client,
|
||||||
dynamicClient,
|
dynamicClient,
|
||||||
typeChecker,
|
|
||||||
cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())),
|
cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())),
|
||||||
NewMatcher(matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)),
|
NewMatcher(matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)),
|
||||||
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy](
|
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy](
|
||||||
|
@@ -27,10 +27,8 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||||
@@ -61,11 +59,6 @@ type policyController struct {
|
|||||||
|
|
||||||
newValidator
|
newValidator
|
||||||
|
|
||||||
// The TypeCheck checks the policy's expressions for type errors.
|
|
||||||
// Type of params is defined in policy.Spec.ParamsKind
|
|
||||||
// Types of object are calculated from policy.Spec.MatchingConstraints
|
|
||||||
typeChecker *TypeChecker
|
|
||||||
|
|
||||||
// Lock which protects:
|
// Lock which protects:
|
||||||
// - cachedPolicies
|
// - cachedPolicies
|
||||||
// - paramCRDControllers
|
// - paramCRDControllers
|
||||||
@@ -103,7 +96,6 @@ func newPolicyController(
|
|||||||
restMapper meta.RESTMapper,
|
restMapper meta.RESTMapper,
|
||||||
client kubernetes.Interface,
|
client kubernetes.Interface,
|
||||||
dynamicClient dynamic.Interface,
|
dynamicClient dynamic.Interface,
|
||||||
typeChecker *TypeChecker,
|
|
||||||
filterCompiler cel.FilterCompiler,
|
filterCompiler cel.FilterCompiler,
|
||||||
matcher Matcher,
|
matcher Matcher,
|
||||||
policiesInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicy],
|
policiesInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicy],
|
||||||
@@ -112,7 +104,6 @@ func newPolicyController(
|
|||||||
res := &policyController{}
|
res := &policyController{}
|
||||||
*res = policyController{
|
*res = policyController{
|
||||||
filterCompiler: filterCompiler,
|
filterCompiler: filterCompiler,
|
||||||
typeChecker: typeChecker,
|
|
||||||
definitionInfo: make(map[namespacedName]*definitionInfo),
|
definitionInfo: make(map[namespacedName]*definitionInfo),
|
||||||
bindingInfos: make(map[namespacedName]*bindingInfo),
|
bindingInfos: make(map[namespacedName]*bindingInfo),
|
||||||
paramsCRDControllers: make(map[v1alpha1.ParamKind]*paramInfo),
|
paramsCRDControllers: make(map[v1alpha1.ParamKind]*paramInfo),
|
||||||
@@ -174,12 +165,6 @@ func (c *policyController) reconcilePolicyDefinition(namespace, name string, def
|
|||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
err := c.reconcilePolicyDefinitionSpec(namespace, name, definition)
|
err := c.reconcilePolicyDefinitionSpec(namespace, name, definition)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c.typeChecker != nil {
|
|
||||||
err = c.reconcilePolicyStatus(namespace, name, definition)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,30 +423,6 @@ func (c *policyController) reconcilePolicyBinding(namespace, name string, bindin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *policyController) reconcilePolicyStatus(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
|
||||||
if definition != nil && definition.Status.ObservedGeneration < definition.Generation {
|
|
||||||
st := c.calculatePolicyStatus(definition)
|
|
||||||
newDefinition := definition.DeepCopy()
|
|
||||||
newDefinition.Status = *st
|
|
||||||
_, err := c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().UpdateStatus(c.context, newDefinition, metav1.UpdateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
// ignore error when the controller is not able to
|
|
||||||
// mutate the definition, and to avoid infinite requeue.
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *policyController) calculatePolicyStatus(definition *v1alpha1.ValidatingAdmissionPolicy) *v1alpha1.ValidatingAdmissionPolicyStatus {
|
|
||||||
expressionWarnings := c.typeChecker.Check(definition)
|
|
||||||
// modifying a deepcopy of the original status, preserving unrelated existing data
|
|
||||||
status := definition.Status.DeepCopy()
|
|
||||||
status.ObservedGeneration = definition.Generation
|
|
||||||
status.TypeChecking = &v1alpha1.TypeChecking{ExpressionWarnings: expressionWarnings}
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *policyController) reconcileParams(namespace, name string, params runtime.Object) error {
|
func (c *policyController) reconcileParams(namespace, name string, params runtime.Object) error {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
// When we add informational type checking we will need to compile in the
|
// When we add informational type checking we will need to compile in the
|
||||||
|
@@ -44,8 +44,8 @@ import (
|
|||||||
const maxTypesToCheck = 10
|
const maxTypesToCheck = 10
|
||||||
|
|
||||||
type TypeChecker struct {
|
type TypeChecker struct {
|
||||||
schemaResolver resolver.SchemaResolver
|
SchemaResolver resolver.SchemaResolver
|
||||||
restMapper meta.RESTMapper
|
RestMapper meta.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypeCheckingContext holds information about the policy being type-checked.
|
// TypeCheckingContext holds information about the policy being type-checked.
|
||||||
@@ -196,7 +196,7 @@ func (c *TypeChecker) declType(gvk schema.GroupVersionKind) (*apiservercel.DeclT
|
|||||||
if gvk.Empty() {
|
if gvk.Empty() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
s, err := c.schemaResolver.ResolveSchema(gvk)
|
s, err := c.SchemaResolver.ResolveSchema(gvk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -266,7 +266,7 @@ func (c *TypeChecker) typesToCheck(p *v1alpha1.ValidatingAdmissionPolicy) []sche
|
|||||||
Version: version,
|
Version: version,
|
||||||
Resource: resource,
|
Resource: resource,
|
||||||
}
|
}
|
||||||
resolved, err := c.restMapper.KindsFor(gvr)
|
resolved, err := c.RestMapper.KindsFor(gvr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@@ -435,8 +435,8 @@ func buildTypeChecker(schemaToReturn *spec.Schema) *TypeChecker {
|
|||||||
restMapper.Add(must3(scheme.ObjectKinds(&appsv1.Deployment{}))[0], meta.RESTScopeRoot)
|
restMapper.Add(must3(scheme.ObjectKinds(&appsv1.Deployment{}))[0], meta.RESTScopeRoot)
|
||||||
|
|
||||||
return &TypeChecker{
|
return &TypeChecker{
|
||||||
schemaResolver: &fakeSchemaResolver{schemaToReturn: schemaToReturn},
|
SchemaResolver: &fakeSchemaResolver{schemaToReturn: schemaToReturn},
|
||||||
restMapper: restMapper,
|
RestMapper: restMapper,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -165,6 +165,9 @@ type KubeControllerManagerConfiguration struct {
|
|||||||
// TTLAfterFinishedControllerConfiguration holds configuration for
|
// TTLAfterFinishedControllerConfiguration holds configuration for
|
||||||
// TTLAfterFinishedController related features.
|
// TTLAfterFinishedController related features.
|
||||||
TTLAfterFinishedController TTLAfterFinishedControllerConfiguration
|
TTLAfterFinishedController TTLAfterFinishedControllerConfiguration
|
||||||
|
// ValidatingAdmissionPolicyStatusControllerConfiguration holds configuration for
|
||||||
|
// ValidatingAdmissionPolicyStatusController related features.
|
||||||
|
ValidatingAdmissionPolicyStatusController ValidatingAdmissionPolicyStatusControllerConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachDetachControllerConfiguration contains elements describing AttachDetachController.
|
// AttachDetachControllerConfiguration contains elements describing AttachDetachController.
|
||||||
@@ -481,3 +484,12 @@ type TTLAfterFinishedControllerConfiguration struct {
|
|||||||
// allowed to sync concurrently.
|
// allowed to sync concurrently.
|
||||||
ConcurrentTTLSyncs int32
|
ConcurrentTTLSyncs int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidatingAdmissionPolicyStatusControllerConfiguration contains elements describing ValidatingAdmissionPolicyStatusController.
|
||||||
|
type ValidatingAdmissionPolicyStatusControllerConfiguration struct {
|
||||||
|
// ConcurrentPolicySyncs is the number of policy objects that are
|
||||||
|
// allowed to sync concurrently. Larger number = quicker type checking,
|
||||||
|
// but more CPU (and network) load.
|
||||||
|
// The default value is 5.
|
||||||
|
ConcurrentPolicySyncs int32
|
||||||
|
}
|
||||||
|
@@ -322,6 +322,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa
|
|||||||
out.SAController = in.SAController
|
out.SAController = in.SAController
|
||||||
out.ServiceController = in.ServiceController
|
out.ServiceController = in.ServiceController
|
||||||
out.TTLAfterFinishedController = in.TTLAfterFinishedController
|
out.TTLAfterFinishedController = in.TTLAfterFinishedController
|
||||||
|
out.ValidatingAdmissionPolicyStatusController = in.ValidatingAdmissionPolicyStatusController
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,6 +570,22 @@ func (in *TTLAfterFinishedControllerConfiguration) DeepCopy() *TTLAfterFinishedC
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ValidatingAdmissionPolicyStatusControllerConfiguration) DeepCopyInto(out *ValidatingAdmissionPolicyStatusControllerConfiguration) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingAdmissionPolicyStatusControllerConfiguration.
|
||||||
|
func (in *ValidatingAdmissionPolicyStatusControllerConfiguration) DeepCopy() *ValidatingAdmissionPolicyStatusControllerConfiguration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ValidatingAdmissionPolicyStatusControllerConfiguration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *VolumeConfiguration) DeepCopyInto(out *VolumeConfiguration) {
|
func (in *VolumeConfiguration) DeepCopyInto(out *VolumeConfiguration) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
"github.com/onsi/gomega"
|
||||||
|
|
||||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||||
@@ -112,6 +113,81 @@ var _ = SIGDescribe("ValidatingAdmissionPolicy [Privileged:ClusterAdmin][Alpha][
|
|||||||
framework.ExpectNoError(err, "create non-replicated ReplicaSet")
|
framework.ExpectNoError(err, "create non-replicated ReplicaSet")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should type check validation expressions", func(ctx context.Context) {
|
||||||
|
var policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy
|
||||||
|
ginkgo.By("creating the policy with correct types", func() {
|
||||||
|
policy = newValidatingAdmissionPolicyBuilder(f.UniqueName+".correct-policy.example.com").
|
||||||
|
MatchUniqueNamespace(f.UniqueName).
|
||||||
|
StartResourceRule().
|
||||||
|
MatchResource([]string{"apps"}, []string{"v1"}, []string{"deployments"}).
|
||||||
|
EndResourceRule().
|
||||||
|
WithValidation(admissionregistrationv1alpha1.Validation{
|
||||||
|
Expression: "object.spec.replicas > 1",
|
||||||
|
}).
|
||||||
|
Build()
|
||||||
|
var err error
|
||||||
|
policy, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, policy, metav1.CreateOptions{})
|
||||||
|
framework.ExpectNoError(err, "create policy")
|
||||||
|
ginkgo.DeferCleanup(func(ctx context.Context, name string) error {
|
||||||
|
return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}, policy.Name)
|
||||||
|
})
|
||||||
|
ginkgo.By("waiting for the type check to finish without any warnings", func() {
|
||||||
|
err := wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||||
|
policy, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Get(ctx, policy.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if policy.Status.TypeChecking != nil { // non-nil TypeChecking indicates its completion
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
framework.ExpectNoError(err, "wait for type checking")
|
||||||
|
gomega.Expect(policy.Status.TypeChecking.ExpressionWarnings).To(gomega.BeEmpty())
|
||||||
|
})
|
||||||
|
ginkgo.By("creating the policy with type confusion", func() {
|
||||||
|
policy = newValidatingAdmissionPolicyBuilder(f.UniqueName+".confused-policy.example.com").
|
||||||
|
MatchUniqueNamespace(f.UniqueName).
|
||||||
|
StartResourceRule().
|
||||||
|
MatchResource([]string{"apps"}, []string{"v1"}, []string{"deployments"}).
|
||||||
|
EndResourceRule().
|
||||||
|
WithValidation(admissionregistrationv1alpha1.Validation{
|
||||||
|
Expression: "object.spec.replicas > '1'", // confusion: int > string
|
||||||
|
MessageExpression: "'wants replicas > 1, got ' + object.spec.replicas", // confusion: string + int
|
||||||
|
}).
|
||||||
|
Build()
|
||||||
|
var err error
|
||||||
|
policy, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, policy, metav1.CreateOptions{})
|
||||||
|
framework.ExpectNoError(err, "create policy")
|
||||||
|
ginkgo.DeferCleanup(func(ctx context.Context, name string) error {
|
||||||
|
return client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}, policy.Name)
|
||||||
|
})
|
||||||
|
ginkgo.By("waiting for the type check to finish with warnings", func() {
|
||||||
|
err := wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||||
|
policy, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Get(ctx, policy.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if policy.Status.TypeChecking != nil { // non-nil TypeChecking indicates its completion
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
framework.ExpectNoError(err, "wait for type checking")
|
||||||
|
|
||||||
|
// assert it to contain 2 warnings, first for expression and second for messageExpression
|
||||||
|
gomega.Expect(policy.Status.TypeChecking.ExpressionWarnings).To(gomega.HaveLen(2))
|
||||||
|
warning := policy.Status.TypeChecking.ExpressionWarnings[0]
|
||||||
|
gomega.Expect(warning.FieldRef).To(gomega.Equal("spec.validations[0].expression"))
|
||||||
|
gomega.Expect(warning.Warning).To(gomega.ContainSubstring("found no matching overload for '_>_' applied to '(int, string)'"))
|
||||||
|
warning = policy.Status.TypeChecking.ExpressionWarnings[1]
|
||||||
|
gomega.Expect(warning.FieldRef).To(gomega.Equal("spec.validations[0].messageExpression"))
|
||||||
|
gomega.Expect(warning.Warning).To(gomega.ContainSubstring("found no matching overload for '_+_' applied to '(string, int)'"))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
func createBinding(bindingName string, uniqueLabel string, policyName string) *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding {
|
func createBinding(bindingName string, uniqueLabel string, policyName string) *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding {
|
||||||
|
@@ -2622,26 +2622,6 @@ func withPolicyExistsLabels(labels []string, policy *admissionregistrationv1alph
|
|||||||
return policy
|
return policy
|
||||||
}
|
}
|
||||||
|
|
||||||
func withGVRMatch(groups []string, versions []string, resources []string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
|
||||||
policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{
|
|
||||||
ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{
|
|
||||||
{
|
|
||||||
RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{
|
|
||||||
Operations: []admissionregistrationv1.OperationType{
|
|
||||||
"*",
|
|
||||||
},
|
|
||||||
Rule: admissionregistrationv1.Rule{
|
|
||||||
APIGroups: groups,
|
|
||||||
APIVersions: versions,
|
|
||||||
Resources: resources,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return policy
|
|
||||||
}
|
|
||||||
|
|
||||||
func withValidations(validations []admissionregistrationv1alpha1.Validation, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
func withValidations(validations []admissionregistrationv1alpha1.Validation, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
policy.Spec.Validations = validations
|
policy.Spec.Validations = validations
|
||||||
return policy
|
return policy
|
||||||
@@ -2917,117 +2897,6 @@ rules:
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidatingAdmissionPolicyTypeChecking(t *testing.T) {
|
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
|
|
||||||
server, err := apiservertesting.StartTestServer(t, nil, []string{
|
|
||||||
"--enable-admission-plugins", "ValidatingAdmissionPolicy",
|
|
||||||
}, framework.SharedEtcd())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer server.TearDownFn()
|
|
||||||
|
|
||||||
config := server.ClientConfig
|
|
||||||
|
|
||||||
client, err := clientset.NewForConfig(config)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy
|
|
||||||
assertFieldRef func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) // warning.fieldRef
|
|
||||||
assertWarnings func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) // warning.warning
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "deployment with correct expression",
|
|
||||||
policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1alpha1.Validation{
|
|
||||||
{
|
|
||||||
Expression: "object.spec.replicas > 1",
|
|
||||||
},
|
|
||||||
}, makePolicy("replicated-deployment"))),
|
|
||||||
assertFieldRef: toHasLengthOf(0),
|
|
||||||
assertWarnings: toHasLengthOf(0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "deployment with type confusion",
|
|
||||||
policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1alpha1.Validation{
|
|
||||||
{
|
|
||||||
Expression: "object.spec.replicas < 100", // this one passes
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Expression: "object.spec.replicas > '1'", // '1' should be int
|
|
||||||
},
|
|
||||||
}, makePolicy("confused-deployment"))),
|
|
||||||
assertFieldRef: toBe("spec.validations[1].expression"),
|
|
||||||
assertWarnings: toHasSubstring(`found no matching overload for '_>_' applied to '(int, string)'`),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
policy, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, tc.policy, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Delete(context.Background(), policy.Name, metav1.DeleteOptions{})
|
|
||||||
err = wait.PollImmediateWithContext(ctx, time.Second, time.Minute, func(ctx context.Context) (done bool, err error) {
|
|
||||||
name := policy.Name
|
|
||||||
// wait until the typeChecking is set, which means the type checking
|
|
||||||
// is complete.
|
|
||||||
updated, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Get(ctx, name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if updated.Status.TypeChecking != nil {
|
|
||||||
policy = updated
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tc.assertFieldRef(policy.Status.TypeChecking.ExpressionWarnings, t)
|
|
||||||
tc.assertWarnings(policy.Status.TypeChecking.ExpressionWarnings, t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toBe(expected ...string) func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
|
||||||
return func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
|
||||||
if len(expected) != len(warnings) {
|
|
||||||
t.Fatalf("mismatched length, expect %d, got %d", len(expected), len(warnings))
|
|
||||||
}
|
|
||||||
for i := range expected {
|
|
||||||
if expected[i] != warnings[i].FieldRef {
|
|
||||||
t.Errorf("expected %q but got %q", expected[i], warnings[i].FieldRef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toHasSubstring(substrings ...string) func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
|
||||||
return func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
|
||||||
if len(substrings) != len(warnings) {
|
|
||||||
t.Fatalf("mismatched length, expect %d, got %d", len(substrings), len(warnings))
|
|
||||||
}
|
|
||||||
for i := range substrings {
|
|
||||||
if !strings.Contains(warnings[i].Warning, substrings[i]) {
|
|
||||||
t.Errorf("missing expected substring %q in %v", substrings[i], warnings[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toHasLengthOf(n int) func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
|
||||||
return func(warnings []admissionregistrationv1alpha1.ExpressionWarning, t *testing.T) {
|
|
||||||
if n != len(warnings) {
|
|
||||||
t.Fatalf("mismatched length, expect %d, got %d", n, len(warnings))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthorizationDecisionCaching(t *testing.T) {
|
func TestAuthorizationDecisionCaching(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
Reference in New Issue
Block a user