diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/BUILD index f181961d86c..62664170f4f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/BUILD @@ -60,7 +60,10 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/v1:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/github.com/hashicorp/golang-lru:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go index f2a0f8d0b0e..76049950904 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission.go @@ -26,6 +26,7 @@ import ( "k8s.io/apiserver/pkg/admission" genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" resourcequotaapi "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota" + v1 "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/v1" "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation" quota "k8s.io/apiserver/pkg/quota/v1" "k8s.io/apiserver/pkg/quota/v1/generic" @@ -36,6 +37,8 @@ import ( // PluginName is a string with the name of the plugin const PluginName = "ResourceQuota" +var namespaceGVK = v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() + // Register registers a plugin func Register(plugins *admission.Plugins) { plugins.Register(PluginName, @@ -136,9 +139,13 @@ func (a *QuotaAdmission) Validate(ctx context.Context, attr admission.Attributes if attr.GetSubresource() != "" { return nil } - // ignore all operations that are not namespaced - if attr.GetNamespace() == "" { + // ignore all operations that are not namespaced or creation of namespaces + if attr.GetNamespace() == "" || isNamespaceCreation(attr) { return nil } return a.evaluator.Evaluate(attr) } + +func isNamespaceCreation(attr admission.Attributes) bool { + return attr.GetOperation() == admission.Create && attr.GetKind().GroupKind() == namespaceGVK +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go index 461eb9bb79e..04f12af8094 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/resourcequota/admission_test.go @@ -17,10 +17,15 @@ limitations under the License. package resourcequota import ( + "context" + "errors" "testing" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + v1 "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/v1" ) func TestPrettyPrint(t *testing.T) { @@ -122,3 +127,37 @@ func TestHasUsageStats(t *testing.T) { } } } + +type fakeEvaluator struct{} + +func (fakeEvaluator) Evaluate(a admission.Attributes) error { + return errors.New("should not be called") +} + +func TestExcludedOperations(t *testing.T) { + a := &QuotaAdmission{ + evaluator: fakeEvaluator{}, + } + testCases := []struct { + desc string + attr admission.Attributes + }{ + { + "subresource", + admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "namespace", "name", schema.GroupVersionResource{}, "subresource", admission.Create, nil, false, nil), + }, + { + "non-namespaced resource", + admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "namespace", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil), + }, + { + "namespace creation", + admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace"), "namespace", "namespace", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil), + }, + } + for _, test := range testCases { + if err := a.Validate(context.TODO(), test.attr, nil); err != nil { + t.Errorf("Test case: %q. Expected no error but got: %v", test.desc, err) + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go index 653c1ff82a8..0ccdb8be1c5 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go @@ -44,6 +44,8 @@ import ( utiltrace "k8s.io/utils/trace" ) +var namespaceGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"} + func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { // For performance tracking purposes. @@ -76,7 +78,6 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int ctx, cancel := context.WithTimeout(req.Context(), timeout) defer cancel() - ctx = request.WithNamespace(ctx, namespace) outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) if err != nil { scope.err(err, w, req) @@ -128,17 +129,21 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int } trace.Step("Conversion done") + // On create, get name from new object if unset + if len(name) == 0 { + _, name, _ = scope.Namer.ObjectName(obj) + } + if len(namespace) == 0 && *gvk == namespaceGVK { + namespace = name + } + ctx = request.WithNamespace(ctx, namespace) + ae := request.AuditEventFrom(ctx) admit = admission.WithAudit(admit, ae) audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer) userInfo, _ := request.UserFrom(ctx) - // On create, get name from new object if unset - if len(name) == 0 { - _, name, _ = scope.Namer.ObjectName(obj) - } - trace.Step("About to store object in database") admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo) requestFunc := func() (runtime.Object, error) { diff --git a/test/integration/apiserver/admissionwebhook/admission_test.go b/test/integration/apiserver/admissionwebhook/admission_test.go index f73a9dc9984..9d30690037e 100644 --- a/test/integration/apiserver/admissionwebhook/admission_test.go +++ b/test/integration/apiserver/admissionwebhook/admission_test.go @@ -218,8 +218,8 @@ func (h *holder) reset(t *testing.T) { } } func (h *holder) expect(gvr schema.GroupVersionResource, gvk, optionsGVK schema.GroupVersionKind, operation v1beta1.Operation, name, namespace string, object, oldObject, options bool) { - // Special-case namespaces, since the object name shows up in request attributes for update/delete requests - if len(namespace) == 0 && gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" && operation != v1beta1.Create { + // Special-case namespaces, since the object name shows up in request attributes + if len(namespace) == 0 && gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" { namespace = name }