Change:
We should have a single NamespaceLifecycle plugin that enforces the Namespace rules now that all providers are off NamespaceAutoProvision. We may also want to make this no longer a user choice to configure and hard-wire the server to always run this check first to simplify configuration errors. For an example

Discussion:
- [SecurityContextDeny](#securitycontextdeny) - [ResourceQuota](#resourcequota) - [LimitRanger](#limitranger) <del> - [NamespaceExists](#namespaceexists) </del> <ins> - [NamespaceExists (deprecated)](#namespaceexists-deprecated) </ins> - [NamespaceAutoProvision (deprecated)](#namespaceautoprovision-deprecated) - [NamespaceLifecycle](#namespacelifecycle) - [Is there a recommended set of plug-ins to use?](#is-there-a-recommended-set-of-plug-ins-to-use)

See the [limitRange design doc](../design/admission_control_limit_range.md) and the [example of Limit Range](limitrange/) for more details. <del> ### NamespaceExists </del> <ins> ### NamespaceExists (deprecated) </ins> This plug-in will observe all incoming requests that attempt to create a resource in a Kubernetes `Namespace` and reject the request if the `Namespace` was not previously created.  We strongly recommend running this plug-in to ensure integrity of your data. <ins> The functionality of this admission controller has been merged into `NamespaceLifecycle` </ins> ### NamespaceAutoProvision (deprecated) This plug-in will observe all incoming requests that attempt to create a resource in a Kubernetes `Namespace` and create a new `Namespace` if one did not already exist previously. <del> We strongly recommend `NamespaceExists` over `NamespaceAutoProvision`. </del> <ins> We strongly recommend `NamespaceLifecycle` over `NamespaceAutoProvision`. </ins> ### NamespaceLifecycle <del> This plug-in enforces that a `Namespace` that is undergoing termination cannot have new objects created in it. </del> <ins> This plug-in enforces that a `Namespace` that is undergoing termination cannot have new objects created in it, and ensures that requests in a non-existant `Namespace` are rejected. </ins> A `Namespace` deletion kicks off a sequence of operations that remove all objects (pods, services, etc.) in that namespace.  In order to enforce integrity of that process, we strongly recommend running this plug-in. <del> Once `NamespaceAutoProvision` is deprecated, we anticipate `NamespaceLifecycle` and `NamespaceExists` will be merged into a single plug-in that enforces the life-cycle of a `Namespace` in Kubernetes. </del> ## Is there a recommended set of plug-ins to use? Yes.

For Kubernetes 1.0, we strongly recommend running the following set of admission control plug-ins (order matters): ``` <del> --admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota </del> <ins> --admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota </ins> ```

import ( "fmt" "io" <ins> "time" </ins> "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api"

func (l *lifecycle) Admit(a admission.Attributes) (err error) { // prevent deletion of immortal namespaces <del> if a.GetOperation() == admission.Delete { if a.GetKind() == "Namespace" && l.immortalNamespaces.Has(a.GetName()) { return errors.NewForbidden(a.GetKind(), a.GetName(), fmt.Errorf("namespace can never be deleted")) } return nil </del> <ins> if a.GetOperation() == admission.Delete && a.GetKind() == "Namespace" && l.immortalNamespaces.Has(a.GetName()) { return errors.NewForbidden(a.GetKind(), a.GetName(), fmt.Errorf("namespace can never be deleted")) </ins> } defaultVersion, kind, err := api.RESTMapper.VersionAndKindForResource(a.GetResource())

if err != nil { return admission.NewForbidden(a, err) } <ins> // refuse to operate on non-existent namespaces </ins> if !exists { <del> return nil </del> <ins> // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not namespaceObj, err = l.client.Namespaces().Get(a.GetNamespace()) if err != nil { return admission.NewForbidden(a, fmt.Errorf("Namespace %s does not exist", a.GetNamespace())) } </ins> } <del> namespace := namespaceObj.(*api.Namespace) if namespace.Status.Phase != api.NamespaceTerminating { return nil </del> <ins> // ensure that we're not trying to create objects in terminating namespaces if a.GetOperation() == admission.Create { namespace := namespaceObj.(*api.Namespace) if namespace.Status.Phase != api.NamespaceTerminating { return nil } return admission.NewForbidden(a, fmt.Errorf("Unable to create new content in namespace %s because it is being terminated.", a.GetNamespace())) </ins> } <del> return admission.NewForbidden(a, fmt.Errorf("Unable to create new content in namespace %s because it is being terminated.", a.GetNamespace())) </del> <ins> return nil </ins> } // NewLifecycle creates a new namespace lifecycle admission control handler

}, &api.Namespace{}, store, <del> 0, </del> <ins> 5*time.Minute, </ins> ) reflector.Run() return &lifecycle{ <del> Handler:            admission.NewHandler(admission.Create, admission.Delete), </del> <ins> Handler:            admission.NewHandler(admission.Create, admission.Update, admission.Delete), </ins> client:             c, store:              store, immortalNamespaces: util.NewStringSet(api.NamespaceDefault),

package lifecycle import ( <ins> "fmt" </ins> "testing" "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/unversioned/cache" "k8s.io/kubernetes/pkg/client/unversioned/testclient" <ins> "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/watch" </ins> ) // TestAdmission

Phase: api.NamespaceActive, }, } <del> store := cache.NewStore(cache.IndexFuncToKeyFuncAdapter(cache.MetaNamespaceIndexFunc)) </del> <ins> reactFunc := func(action testclient.Action) (runtime.Object, error) { switch { case action.Matches("get", "namespaces"): if getAction, ok := action.(testclient.GetAction); ok && getAction.GetName() == namespaceObj.Name { return namespaceObj, nil } case action.Matches("list", "namespaces"): return &api.NamespaceList{Items: []api.Namespace{*namespaceObj}}, nil } return nil, fmt.Errorf("No result for action %v", action) } store := cache.NewStore(cache.MetaNamespaceKeyFunc) </ins> store.Add(namespaceObj) <del> mockClient := &testclient.Fake{} </del> <ins> fakeWatch := watch.NewFake() mockClient := &testclient.Fake{Watch: fakeWatch, ReactFn: reactFunc} </ins> lfhandler := NewLifecycle(mockClient).(*lifecycle) lfhandler.store = store handler := admission.NewChainHandler(lfhandler) pod := api.Pod{ <del> ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespaceObj.Namespace}, </del> <ins> ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespaceObj.Name}, Spec: api.PodSpec{ Volumes:    []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}}, }, } badPod := api.Pod{ ObjectMeta: api.ObjectMeta{Name: "456", Namespace: "doesnotexist"}, </ins> Spec: api.PodSpec{ Volumes:    []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}},

t.Errorf("Did not expect an error %v", err) } <ins> // verify create/update/delete of object in non-existant namespace throws error err = handler.Admit(admission.NewAttributesRecord(&badPod, "Pod", badPod.Namespace, badPod.Name, "pods", "", admission.Create, nil)) if err == nil { t.Errorf("Expected an aerror that objects cannot be created in non-existant namespaces", err) } err = handler.Admit(admission.NewAttributesRecord(&badPod, "Pod", badPod.Namespace, badPod.Name, "pods", "", admission.Update, nil)) if err == nil { t.Errorf("Expected an aerror that objects cannot be updated in non-existant namespaces", err) } err = handler.Admit(admission.NewAttributesRecord(&badPod, "Pod", badPod.Namespace, badPod.Name, "pods", "", admission.Delete, nil)) if err == nil { t.Errorf("Expected an aerror that objects cannot be deleted in non-existant namespaces", err) } </ins> }