mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
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).
This commit is contained in:
parent
8eda21ea3f
commit
3cb510e33e
@ -60,7 +60,10 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
"//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/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime: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: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/informers:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||||
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
|
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
|
||||||
resourcequotaapi "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota"
|
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"
|
"k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation"
|
||||||
quota "k8s.io/apiserver/pkg/quota/v1"
|
quota "k8s.io/apiserver/pkg/quota/v1"
|
||||||
"k8s.io/apiserver/pkg/quota/v1/generic"
|
"k8s.io/apiserver/pkg/quota/v1/generic"
|
||||||
@ -36,6 +37,8 @@ import (
|
|||||||
// PluginName is a string with the name of the plugin
|
// PluginName is a string with the name of the plugin
|
||||||
const PluginName = "ResourceQuota"
|
const PluginName = "ResourceQuota"
|
||||||
|
|
||||||
|
var namespaceGVK = v1.SchemeGroupVersion.WithKind("Namespace").GroupKind()
|
||||||
|
|
||||||
// Register registers a plugin
|
// Register registers a plugin
|
||||||
func Register(plugins *admission.Plugins) {
|
func Register(plugins *admission.Plugins) {
|
||||||
plugins.Register(PluginName,
|
plugins.Register(PluginName,
|
||||||
@ -136,9 +139,13 @@ func (a *QuotaAdmission) Validate(ctx context.Context, attr admission.Attributes
|
|||||||
if attr.GetSubresource() != "" {
|
if attr.GetSubresource() != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// ignore all operations that are not namespaced
|
// ignore all operations that are not namespaced or creation of namespaces
|
||||||
if attr.GetNamespace() == "" {
|
if attr.GetNamespace() == "" || isNamespaceCreation(attr) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return a.evaluator.Evaluate(attr)
|
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
|
package resourcequota
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"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) {
|
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"
|
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 {
|
func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
// For performance tracking purposes.
|
// 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)
|
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
ctx = request.WithNamespace(ctx, namespace)
|
|
||||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scope.err(err, w, req)
|
scope.err(err, w, req)
|
||||||
@ -128,17 +129,21 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
|||||||
}
|
}
|
||||||
trace.Step("Conversion done")
|
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)
|
ae := request.AuditEventFrom(ctx)
|
||||||
admit = admission.WithAudit(admit, ae)
|
admit = admission.WithAudit(admit, ae)
|
||||||
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
||||||
|
|
||||||
userInfo, _ := request.UserFrom(ctx)
|
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")
|
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)
|
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) {
|
requestFunc := func() (runtime.Object, error) {
|
||||||
|
@ -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) {
|
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
|
// Special-case namespaces, since the object name shows up in request attributes
|
||||||
if len(namespace) == 0 && gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" && operation != v1beta1.Create {
|
if len(namespace) == 0 && gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" {
|
||||||
namespace = name
|
namespace = name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user