Make the creation of namespace using POST and PATCH consistent

PATCH verb is used when creating a namespace using server-side apply,
while POST verb is used when creating a namespace using client-side
apply.

The difference in path between the two ways to create a namespace led to
an inconsistency when calling webhooks. When server-side apply is used,
the request sent to webhooks has the field "namespace" populated with
the name of namespace being created. On the other hand, when using
client-side apply the "namespace" field is omitted.

This commit aims to make the behaviour consistent and populates the
"namespace" field when creating a namespace using POST verb (i.e.
client-side apply).

Kubernetes-commit: 3cb510e33eecbdc37aad14f121396ccfbf5268cb
This commit is contained in:
Andrea Nodari 2020-09-21 12:13:12 +02:00 committed by Kubernetes Publisher
parent fd42eacc6e
commit 618f4b129a
3 changed files with 59 additions and 8 deletions

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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) {