diff --git a/cluster/aws/templates/create-dynamic-salt-files.sh b/cluster/aws/templates/create-dynamic-salt-files.sh index 85c3153968a..682d9fbbc3b 100644 --- a/cluster/aws/templates/create-dynamic-salt-files.sh +++ b/cluster/aws/templates/create-dynamic-salt-files.sh @@ -33,6 +33,7 @@ enable_cluster_dns: '$(echo "$ENABLE_CLUSTER_DNS" | sed -e "s/'/''/g")' dns_replicas: '$(echo "$DNS_REPLICAS" | sed -e "s/'/''/g")' dns_server: '$(echo "$DNS_SERVER_IP" | sed -e "s/'/''/g")' dns_domain: '$(echo "$DNS_DOMAIN" | sed -e "s/'/''/g")' +admission_control: '$(echo "$ADMISSION_CONTROL" | sed -e "s/'/''/g")' EOF mkdir -p /srv/salt-overlay/salt/nginx diff --git a/cluster/gce/config-default.sh b/cluster/gce/config-default.sh index 11c08bf50a4..326e53508ef 100755 --- a/cluster/gce/config-default.sh +++ b/cluster/gce/config-default.sh @@ -105,3 +105,6 @@ ENABLE_CLUSTER_DNS=true DNS_SERVER_IP="10.0.0.10" DNS_DOMAIN="kubernetes.local" DNS_REPLICAS=1 + +# Admission Controllers to invoke prior to persisting objects in cluster +ADMISSION_CONTROL=NamespaceAutoProvision,LimitRanger,ResourceQuota diff --git a/cluster/gce/configure-vm.sh b/cluster/gce/configure-vm.sh index 18e51c53ed2..1929a5ba762 100644 --- a/cluster/gce/configure-vm.sh +++ b/cluster/gce/configure-vm.sh @@ -200,6 +200,7 @@ enable_cluster_dns: '$(echo "$ENABLE_CLUSTER_DNS" | sed -e "s/'/''/g")' dns_replicas: '$(echo "$DNS_REPLICAS" | sed -e "s/'/''/g")' dns_server: '$(echo "$DNS_SERVER_IP" | sed -e "s/'/''/g")' dns_domain: '$(echo "$DNS_DOMAIN" | sed -e "s/'/''/g")' +admission_control: '$(echo "$ADMISSION_CONTROL" | sed -e "s/'/''/g")' EOF if [[ "${KUBERNETES_MASTER}" == "true" ]]; then diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 706aca6f87f..2525cb3058e 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -452,6 +452,7 @@ DNS_REPLICAS: $(yaml-quote ${DNS_REPLICAS:-}) DNS_SERVER_IP: $(yaml-quote ${DNS_SERVER_IP:-}) DNS_DOMAIN: $(yaml-quote ${DNS_DOMAIN:-}) MASTER_HTPASSWD: $(yaml-quote ${MASTER_HTPASSWD}) +ADMISSION_CONTROL: $(yaml-quota ${ADMISSION_CONTROL:-}) EOF if [[ "${master}" != "true" ]]; then diff --git a/cluster/saltbase/salt/kube-apiserver/default b/cluster/saltbase/salt/kube-apiserver/default index bedbb68c42d..64dbb9018fc 100644 --- a/cluster/saltbase/salt/kube-apiserver/default +++ b/cluster/saltbase/salt/kube-apiserver/default @@ -59,8 +59,8 @@ {% endif -%} {% set admission_control = "" -%} -{% if grains.admission_control is defined -%} - {% set admission_control = "--admission_control=" + grains.admission_control -%} +{% if pillar['admission_control'] is defined -%} + {% set admission_control = "--admission_control=" + pillar['admission_control'] -%} {% endif -%} {% set runtime_config = "" -%} diff --git a/cluster/vagrant/config-default.sh b/cluster/vagrant/config-default.sh index f6e91ca1ddf..cf2c5f84a3b 100755 --- a/cluster/vagrant/config-default.sh +++ b/cluster/vagrant/config-default.sh @@ -49,7 +49,7 @@ MASTER_USER=vagrant MASTER_PASSWD=vagrant # Admission Controllers to invoke prior to persisting objects in cluster -ADMISSION_CONTROL=NamespaceExists,LimitRanger,ResourceQuota,AlwaysAdmit +ADMISSION_CONTROL=NamespaceAutoProvision,LimitRanger,ResourceQuota # Optional: Install node monitoring. ENABLE_NODE_MONITORING=true diff --git a/cluster/vagrant/provision-master.sh b/cluster/vagrant/provision-master.sh index 01987e3856b..e938927cf3f 100755 --- a/cluster/vagrant/provision-master.sh +++ b/cluster/vagrant/provision-master.sh @@ -83,7 +83,6 @@ grains: cloud_provider: vagrant roles: - kubernetes-master - admission_control: '$(echo "$ADMISSION_CONTROL" | sed -e "s/'/''/g")' runtime_config: '$(echo "$RUNTIME_CONFIG" | sed -e "s/'/''/g")' EOF @@ -102,6 +101,7 @@ cat </srv/salt-overlay/pillar/cluster-params.sls dns_server: '$(echo "$DNS_SERVER_IP" | sed -e "s/'/''/g")' dns_domain: '$(echo "$DNS_DOMAIN" | sed -e "s/'/''/g")' instance_prefix: '$(echo "$INSTANCE_PREFIX" | sed -e "s/'/''/g")' + admission_control: '$(echo "$ADMISSION_CONTROL" | sed -e "s/'/''/g")' EOF # Configure the salt-master diff --git a/plugin/pkg/admission/namespace/autoprovision/admission.go b/plugin/pkg/admission/namespace/autoprovision/admission.go index 3e18f08c6d9..8f2ed0140ec 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission.go @@ -45,6 +45,10 @@ type provision struct { } func (p *provision) Admit(a admission.Attributes) (err error) { + // only handle create requests + if a.GetOperation() != "CREATE" { + return nil + } defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource()) if err != nil { return err diff --git a/plugin/pkg/admission/namespace/autoprovision/admission_test.go b/plugin/pkg/admission/namespace/autoprovision/admission_test.go index 8c95eac68e5..b6f6bb26974 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission_test.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission_test.go @@ -15,3 +15,91 @@ limitations under the License. */ package autoprovision + +import ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" +) + +// TestAdmission verifies a namespace is created on create requests for namespace managed resources +func TestAdmission(t *testing.T) { + namespace := "test" + mockClient := &client.Fake{} + handler := &provision{ + client: mockClient, + store: cache.NewStore(cache.MetaNamespaceKeyFunc), + } + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image"}}, + }, + } + err := handler.Admit(admission.NewAttributesRecord(&pod, namespace, "pods", "CREATE")) + if err != nil { + t.Errorf("Unexpected error returned from admission handler") + } + if len(mockClient.Actions) != 1 { + t.Errorf("Expected a create-namespace request") + } + if mockClient.Actions[0].Action != "create-namespace" { + t.Errorf("Expected a create-namespace request to be made via the client") + } +} + +// TestAdmissionNamespaceExists verifies that no client call is made when a namespace already exists +func TestAdmissionNamespaceExists(t *testing.T) { + namespace := "test" + mockClient := &client.Fake{} + store := cache.NewStore(cache.MetaNamespaceKeyFunc) + store.Add(&api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: namespace}, + }) + handler := &provision{ + client: mockClient, + store: store, + } + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image"}}, + }, + } + err := handler.Admit(admission.NewAttributesRecord(&pod, namespace, "pods", "CREATE")) + if err != nil { + t.Errorf("Unexpected error returned from admission handler") + } + if len(mockClient.Actions) != 0 { + t.Errorf("No client request should have been made") + } +} + +// TestIgnoreAdmission validates that a request is ignored if its not a create +func TestIgnoreAdmission(t *testing.T) { + namespace := "test" + mockClient := &client.Fake{} + handler := &provision{ + client: mockClient, + store: cache.NewStore(cache.MetaNamespaceKeyFunc), + } + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image"}}, + }, + } + err := handler.Admit(admission.NewAttributesRecord(&pod, namespace, "pods", "UPDATE")) + if err != nil { + t.Errorf("Unexpected error returned from admission handler") + } + if len(mockClient.Actions) != 0 { + t.Errorf("No client request should have been made") + } +}