mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
ingress: Update IngressClass feature and admission controller for v1
Signed-off-by: Christopher M. Luciano <cmluciano@us.ibm.com>
This commit is contained in:
parent
868efab6f5
commit
92506a98fc
@ -568,6 +568,7 @@ const (
|
|||||||
|
|
||||||
// owner: @robscott
|
// owner: @robscott
|
||||||
// beta: v1.18
|
// beta: v1.18
|
||||||
|
// ga: v1.19
|
||||||
//
|
//
|
||||||
// Enables DefaultIngressClass admission controller.
|
// Enables DefaultIngressClass admission controller.
|
||||||
DefaultIngressClass featuregate.Feature = "DefaultIngressClass"
|
DefaultIngressClass featuregate.Feature = "DefaultIngressClass"
|
||||||
@ -678,7 +679,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
ServiceAppProtocol: {Default: true, PreRelease: featuregate.Beta},
|
ServiceAppProtocol: {Default: true, PreRelease: featuregate.Beta},
|
||||||
ImmutableEphemeralVolumes: {Default: true, PreRelease: featuregate.Beta},
|
ImmutableEphemeralVolumes: {Default: true, PreRelease: featuregate.Beta},
|
||||||
DefaultIngressClass: {Default: true, PreRelease: featuregate.Beta},
|
DefaultIngressClass: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
|
||||||
HugePageStorageMediumSize: {Default: false, PreRelease: featuregate.Alpha},
|
HugePageStorageMediumSize: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
ExternalPolicyForExternalIP: {Default: true, PreRelease: featuregate.GA}, // remove in 1.20
|
ExternalPolicyForExternalIP: {Default: true, PreRelease: featuregate.GA}, // remove in 1.20
|
||||||
AnyVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha},
|
AnyVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
@ -7,15 +7,14 @@ go_library(
|
|||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/apis/networking:go_default_library",
|
"//pkg/apis/networking:go_default_library",
|
||||||
"//pkg/features:go_default_library",
|
"//staging/src/k8s.io/api/networking/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/networking/v1beta1:go_default_library",
|
"//staging/src/k8s.io/api/networking/v1beta1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/listers/networking/v1beta1:go_default_library",
|
"//staging/src/k8s.io/client-go/listers/networking/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
|
|
||||||
"//vendor/k8s.io/klog/v2:go_default_library",
|
"//vendor/k8s.io/klog/v2:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -28,6 +27,7 @@ go_test(
|
|||||||
"//pkg/apis/core:go_default_library",
|
"//pkg/apis/core:go_default_library",
|
||||||
"//pkg/apis/networking:go_default_library",
|
"//pkg/apis/networking:go_default_library",
|
||||||
"//pkg/controller:go_default_library",
|
"//pkg/controller:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/networking/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/networking/v1beta1:go_default_library",
|
"//staging/src/k8s.io/api/networking/v1beta1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
@ -21,17 +21,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
networkingv1 "k8s.io/api/networking/v1"
|
||||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
|
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
networkingv1beta1listers "k8s.io/client-go/listers/networking/v1beta1"
|
networkingv1listers "k8s.io/client-go/listers/networking/v1"
|
||||||
"k8s.io/component-base/featuregate"
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/kubernetes/pkg/apis/networking"
|
"k8s.io/kubernetes/pkg/apis/networking"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -50,10 +49,7 @@ func Register(plugins *admission.Plugins) {
|
|||||||
// classDefaulterPlugin holds state for and implements the admission plugin.
|
// classDefaulterPlugin holds state for and implements the admission plugin.
|
||||||
type classDefaulterPlugin struct {
|
type classDefaulterPlugin struct {
|
||||||
*admission.Handler
|
*admission.Handler
|
||||||
lister networkingv1beta1listers.IngressClassLister
|
lister networkingv1listers.IngressClassLister
|
||||||
|
|
||||||
inspectedFeatures bool
|
|
||||||
defaultIngressClassEnabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ admission.Interface = &classDefaulterPlugin{}
|
var _ admission.Interface = &classDefaulterPlugin{}
|
||||||
@ -67,31 +63,16 @@ func newPlugin() *classDefaulterPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InspectFeatureGates allows setting bools without taking a dep on a global variable
|
|
||||||
func (a *classDefaulterPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
|
||||||
a.defaultIngressClassEnabled = featureGates.Enabled(features.DefaultIngressClass)
|
|
||||||
a.inspectedFeatures = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetExternalKubeInformerFactory sets a lister and readyFunc for this
|
// SetExternalKubeInformerFactory sets a lister and readyFunc for this
|
||||||
// classDefaulterPlugin using the provided SharedInformerFactory.
|
// classDefaulterPlugin using the provided SharedInformerFactory.
|
||||||
func (a *classDefaulterPlugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
func (a *classDefaulterPlugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||||
if !a.defaultIngressClassEnabled {
|
informer := f.Networking().V1().IngressClasses()
|
||||||
return
|
|
||||||
}
|
|
||||||
informer := f.Networking().V1beta1().IngressClasses()
|
|
||||||
a.lister = informer.Lister()
|
a.lister = informer.Lister()
|
||||||
a.SetReadyFunc(informer.Informer().HasSynced)
|
a.SetReadyFunc(informer.Informer().HasSynced)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateInitialization ensures lister is set.
|
// ValidateInitialization ensures lister is set.
|
||||||
func (a *classDefaulterPlugin) ValidateInitialization() error {
|
func (a *classDefaulterPlugin) ValidateInitialization() error {
|
||||||
if !a.inspectedFeatures {
|
|
||||||
return fmt.Errorf("InspectFeatureGates was not called")
|
|
||||||
}
|
|
||||||
if !a.defaultIngressClassEnabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if a.lister == nil {
|
if a.lister == nil {
|
||||||
return fmt.Errorf("missing lister")
|
return fmt.Errorf("missing lister")
|
||||||
}
|
}
|
||||||
@ -101,10 +82,7 @@ func (a *classDefaulterPlugin) ValidateInitialization() error {
|
|||||||
// Admit sets the default value of a Ingress's class if the user did not specify
|
// Admit sets the default value of a Ingress's class if the user did not specify
|
||||||
// a class.
|
// a class.
|
||||||
func (a *classDefaulterPlugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
func (a *classDefaulterPlugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||||
if !a.defaultIngressClassEnabled {
|
if attr.GetResource().GroupResource() != networkingv1.Resource("ingresses") {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if attr.GetResource().GroupResource() != networkingv1beta1.Resource("ingresses") {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,13 +125,13 @@ func (a *classDefaulterPlugin) Admit(ctx context.Context, attr admission.Attribu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getDefaultClass returns the default IngressClass from the store, or nil.
|
// getDefaultClass returns the default IngressClass from the store, or nil.
|
||||||
func getDefaultClass(lister networkingv1beta1listers.IngressClassLister) (*networkingv1beta1.IngressClass, error) {
|
func getDefaultClass(lister networkingv1listers.IngressClassLister) (*networkingv1.IngressClass, error) {
|
||||||
list, err := lister.List(labels.Everything())
|
list, err := lister.List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultClasses := []*networkingv1beta1.IngressClass{}
|
defaultClasses := []*networkingv1.IngressClass{}
|
||||||
for _, class := range list {
|
for _, class := range list {
|
||||||
if class.Annotations[networkingv1beta1.AnnotationIsDefaultIngressClass] == "true" {
|
if class.Annotations[networkingv1beta1.AnnotationIsDefaultIngressClass] == "true" {
|
||||||
defaultClasses = append(defaultClasses, class)
|
defaultClasses = append(defaultClasses, class)
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
networkingv1 "k8s.io/api/networking/v1"
|
||||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -35,7 +36,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAdmission(t *testing.T) {
|
func TestAdmission(t *testing.T) {
|
||||||
defaultClass1 := &networkingv1beta1.IngressClass{
|
defaultClass1 := &networkingv1.IngressClass{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "IngressClass",
|
Kind: "IngressClass",
|
||||||
},
|
},
|
||||||
@ -46,7 +47,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defaultClass2 := &networkingv1beta1.IngressClass{
|
defaultClass2 := &networkingv1.IngressClass{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "default2",
|
Name: "default2",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
@ -55,7 +56,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Class that has explicit default = false
|
// Class that has explicit default = false
|
||||||
classWithFalseDefault := &networkingv1beta1.IngressClass{
|
classWithFalseDefault := &networkingv1.IngressClass{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "IngressClass",
|
Kind: "IngressClass",
|
||||||
},
|
},
|
||||||
@ -67,7 +68,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Class with missing default annotation (=non-default)
|
// Class with missing default annotation (=non-default)
|
||||||
classWithNoDefault := &networkingv1beta1.IngressClass{
|
classWithNoDefault := &networkingv1.IngressClass{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "IngressClass",
|
Kind: "IngressClass",
|
||||||
},
|
},
|
||||||
@ -76,7 +77,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Class with empty default annotation (=non-default)
|
// Class with empty default annotation (=non-default)
|
||||||
classWithEmptyDefault := &networkingv1beta1.IngressClass{
|
classWithEmptyDefault := &networkingv1.IngressClass{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "IngressClass",
|
Kind: "IngressClass",
|
||||||
},
|
},
|
||||||
@ -90,7 +91,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
classes []*networkingv1beta1.IngressClass
|
classes []*networkingv1.IngressClass
|
||||||
classField *string
|
classField *string
|
||||||
classAnnotation *string
|
classAnnotation *string
|
||||||
expectedClass *string
|
expectedClass *string
|
||||||
@ -98,7 +99,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no default, no modification of Ingress",
|
name: "no default, no modification of Ingress",
|
||||||
classes: []*networkingv1beta1.IngressClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
classes: []*networkingv1.IngressClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||||
classField: nil,
|
classField: nil,
|
||||||
classAnnotation: nil,
|
classAnnotation: nil,
|
||||||
expectedClass: nil,
|
expectedClass: nil,
|
||||||
@ -106,7 +107,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one default, modify Ingress with class=nil",
|
name: "one default, modify Ingress with class=nil",
|
||||||
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||||
classField: nil,
|
classField: nil,
|
||||||
classAnnotation: nil,
|
classAnnotation: nil,
|
||||||
expectedClass: utilpointer.StringPtr(defaultClass1.Name),
|
expectedClass: utilpointer.StringPtr(defaultClass1.Name),
|
||||||
@ -114,7 +115,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one default, no modification of Ingress with class field=''",
|
name: "one default, no modification of Ingress with class field=''",
|
||||||
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||||
classField: utilpointer.StringPtr(""),
|
classField: utilpointer.StringPtr(""),
|
||||||
classAnnotation: nil,
|
classAnnotation: nil,
|
||||||
expectedClass: utilpointer.StringPtr(""),
|
expectedClass: utilpointer.StringPtr(""),
|
||||||
@ -122,7 +123,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one default, no modification of Ingress with class field='foo'",
|
name: "one default, no modification of Ingress with class field='foo'",
|
||||||
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||||
classField: utilpointer.StringPtr("foo"),
|
classField: utilpointer.StringPtr("foo"),
|
||||||
classAnnotation: nil,
|
classAnnotation: nil,
|
||||||
expectedClass: utilpointer.StringPtr("foo"),
|
expectedClass: utilpointer.StringPtr("foo"),
|
||||||
@ -130,7 +131,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one default, no modification of Ingress with class annotation='foo'",
|
name: "one default, no modification of Ingress with class annotation='foo'",
|
||||||
classes: []*networkingv1beta1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||||
classField: nil,
|
classField: nil,
|
||||||
classAnnotation: utilpointer.StringPtr("foo"),
|
classAnnotation: utilpointer.StringPtr("foo"),
|
||||||
expectedClass: nil,
|
expectedClass: nil,
|
||||||
@ -138,15 +139,15 @@ func TestAdmission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "two defaults, error with Ingress with class field=nil",
|
name: "two defaults, error with Ingress with class field=nil",
|
||||||
classes: []*networkingv1beta1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
classes: []*networkingv1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||||
classField: nil,
|
classField: nil,
|
||||||
classAnnotation: nil,
|
classAnnotation: nil,
|
||||||
expectedClass: nil,
|
expectedClass: nil,
|
||||||
expectedError: errors.NewForbidden(networkingv1beta1.Resource("ingresses"), "testing", errors.NewInternalError(fmt.Errorf("2 default IngressClasses were found, only 1 allowed"))),
|
expectedError: errors.NewForbidden(networkingv1.Resource("ingresses"), "testing", errors.NewInternalError(fmt.Errorf("2 default IngressClasses were found, only 1 allowed"))),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "two defaults, no modification with Ingress with class field=''",
|
name: "two defaults, no modification with Ingress with class field=''",
|
||||||
classes: []*networkingv1beta1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
classes: []*networkingv1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||||
classField: utilpointer.StringPtr(""),
|
classField: utilpointer.StringPtr(""),
|
||||||
classAnnotation: nil,
|
classAnnotation: nil,
|
||||||
expectedClass: utilpointer.StringPtr(""),
|
expectedClass: utilpointer.StringPtr(""),
|
||||||
@ -157,11 +158,10 @@ func TestAdmission(t *testing.T) {
|
|||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
ctrl := newPlugin()
|
ctrl := newPlugin()
|
||||||
ctrl.defaultIngressClassEnabled = true
|
|
||||||
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
|
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
|
||||||
ctrl.SetExternalKubeInformerFactory(informerFactory)
|
ctrl.SetExternalKubeInformerFactory(informerFactory)
|
||||||
for _, c := range testCase.classes {
|
for _, c := range testCase.classes {
|
||||||
informerFactory.Networking().V1beta1().IngressClasses().Informer().GetStore().Add(c)
|
informerFactory.Networking().V1().IngressClasses().Informer().GetStore().Add(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
ingress := &networking.Ingress{ObjectMeta: metav1.ObjectMeta{Name: "testing", Namespace: "testing"}}
|
ingress := &networking.Ingress{ObjectMeta: metav1.ObjectMeta{Name: "testing", Namespace: "testing"}}
|
||||||
@ -178,7 +178,7 @@ func TestAdmission(t *testing.T) {
|
|||||||
api.Kind("Ingress").WithVersion("version"),
|
api.Kind("Ingress").WithVersion("version"),
|
||||||
ingress.Namespace,
|
ingress.Namespace,
|
||||||
ingress.Name,
|
ingress.Name,
|
||||||
networkingv1beta1.Resource("ingresses").WithVersion("version"),
|
networkingv1.Resource("ingresses").WithVersion("version"),
|
||||||
"", // subresource
|
"", // subresource
|
||||||
admission.Create,
|
admission.Create,
|
||||||
&metav1.CreateOptions{},
|
&metav1.CreateOptions{},
|
||||||
|
Loading…
Reference in New Issue
Block a user