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:
parent
fd42eacc6e
commit
618f4b129a
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue