diff --git a/cluster/centos/config-default.sh b/cluster/centos/config-default.sh index d73364c976..c66c839269 100755 --- a/cluster/centos/config-default.sh +++ b/cluster/centos/config-default.sh @@ -120,7 +120,7 @@ export FLANNEL_NET=${FLANNEL_NET:-"172.16.0.0/16"} # Admission Controllers to invoke prior to persisting objects in cluster # If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -export ADMISSION_CONTROL=${ADMISSION_CONTROL:-"NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultTolerationSeconds,ResourceQuota"} +export ADMISSION_CONTROL=${ADMISSION_CONTROL:-"Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultTolerationSeconds,ResourceQuota"} # Extra options to set on the Docker command line. # This is useful for setting --insecure-registry for local registries. diff --git a/cluster/gce/config-default.sh b/cluster/gce/config-default.sh index 8220e04a48..2358d52d10 100755 --- a/cluster/gce/config-default.sh +++ b/cluster/gce/config-default.sh @@ -219,7 +219,7 @@ fi # Admission Controllers to invoke prior to persisting objects in cluster # If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota +ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota # Optional: if set to true kube-up will automatically check for existing resources and clean them up. KUBE_UP_AUTOMATIC_CLEANUP=${KUBE_UP_AUTOMATIC_CLEANUP:-false} diff --git a/cluster/gce/config-test.sh b/cluster/gce/config-test.sh index c408487cfd..76cccb6ec2 100755 --- a/cluster/gce/config-test.sh +++ b/cluster/gce/config-test.sh @@ -249,7 +249,7 @@ if [ ${ENABLE_IP_ALIASES} = true ]; then fi # If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -ADMISSION_CONTROL="${KUBE_ADMISSION_CONTROL:-NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,PodPreset,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota}" +ADMISSION_CONTROL="${KUBE_ADMISSION_CONTROL:-Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,PodPreset,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota}" # Optional: if set to true kube-up will automatically check for existing resources and clean them up. KUBE_UP_AUTOMATIC_CLEANUP=${KUBE_UP_AUTOMATIC_CLEANUP:-false} diff --git a/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py b/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py index fbe2b45af7..226a12d738 100644 --- a/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py +++ b/cluster/juju/layers/kubernetes-master/reactive/kubernetes_master.py @@ -791,6 +791,7 @@ def configure_master_services(): api_opts.add('insecure-port', '8080') api_opts.add('storage-backend', 'etcd2') # FIXME: add etcd3 support admission_control = [ + 'Initializers', 'NamespaceLifecycle', 'LimitRanger', 'ServiceAccount', diff --git a/cluster/libvirt-coreos/util.sh b/cluster/libvirt-coreos/util.sh index be4589807d..334557ea93 100644 --- a/cluster/libvirt-coreos/util.sh +++ b/cluster/libvirt-coreos/util.sh @@ -27,7 +27,7 @@ source "$KUBE_ROOT/cluster/common.sh" export LIBVIRT_DEFAULT_URI=qemu:///system export SERVICE_ACCOUNT_LOOKUP=${SERVICE_ACCOUNT_LOOKUP:-true} -export ADMISSION_CONTROL=${ADMISSION_CONTROL:-NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota} +export ADMISSION_CONTROL=${ADMISSION_CONTROL:-Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota} readonly POOL=kubernetes readonly POOL_PATH=/var/lib/libvirt/images/kubernetes diff --git a/cluster/openstack-heat/kubernetes-heat/fragments/configure-salt.yaml b/cluster/openstack-heat/kubernetes-heat/fragments/configure-salt.yaml index 4f526b6f2c..04862affe4 100644 --- a/cluster/openstack-heat/kubernetes-heat/fragments/configure-salt.yaml +++ b/cluster/openstack-heat/kubernetes-heat/fragments/configure-salt.yaml @@ -57,7 +57,7 @@ write_files: dns_domain: cluster.local enable_dns_horizontal_autoscaler: "false" instance_prefix: kubernetes - admission_control: NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota + admission_control: Initializers,NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota enable_cpu_cfs_quota: "true" network_provider: none cluster_cidr: "$cluster_cidr" diff --git a/cluster/photon-controller/templates/create-dynamic-salt-files.sh b/cluster/photon-controller/templates/create-dynamic-salt-files.sh index 86af586503..4319f02e3b 100755 --- a/cluster/photon-controller/templates/create-dynamic-salt-files.sh +++ b/cluster/photon-controller/templates/create-dynamic-salt-files.sh @@ -122,5 +122,5 @@ dns_domain: $DNS_DOMAIN e2e_storage_test_environment: "${E2E_STORAGE_TEST_ENVIRONMENT:-false}" cluster_cidr: "$NODE_IP_RANGES" allocate_node_cidrs: "${ALLOCATE_NODE_CIDRS:-true}" -admission_control: NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota +admission_control: Initializers,NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota EOF diff --git a/cluster/vagrant/config-default.sh b/cluster/vagrant/config-default.sh index c307b8bd38..7eea6e8e77 100755 --- a/cluster/vagrant/config-default.sh +++ b/cluster/vagrant/config-default.sh @@ -56,7 +56,7 @@ MASTER_PASSWD="${MASTER_PASSWD:-vagrant}" # Admission Controllers to invoke prior to persisting objects in cluster # If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely. -ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota +ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota # Optional: Enable node logging. ENABLE_NODE_LOGGING=false diff --git a/cmd/kube-apiserver/app/BUILD b/cmd/kube-apiserver/app/BUILD index ed1ca4bbd8..2b08f9e210 100644 --- a/cmd/kube-apiserver/app/BUILD +++ b/cmd/kube-apiserver/app/BUILD @@ -52,6 +52,7 @@ go_library( "//plugin/pkg/admission/exec:go_default_library", "//plugin/pkg/admission/gc:go_default_library", "//plugin/pkg/admission/imagepolicy:go_default_library", + "//plugin/pkg/admission/initialization:go_default_library", "//plugin/pkg/admission/initialresources:go_default_library", "//plugin/pkg/admission/limitranger:go_default_library", "//plugin/pkg/admission/namespace/autoprovision:go_default_library", diff --git a/cmd/kube-apiserver/app/plugins.go b/cmd/kube-apiserver/app/plugins.go index 6818b28ddf..7f69f6b766 100644 --- a/cmd/kube-apiserver/app/plugins.go +++ b/cmd/kube-apiserver/app/plugins.go @@ -33,6 +33,7 @@ import ( "k8s.io/kubernetes/plugin/pkg/admission/exec" "k8s.io/kubernetes/plugin/pkg/admission/gc" "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy" + "k8s.io/kubernetes/plugin/pkg/admission/initialization" "k8s.io/kubernetes/plugin/pkg/admission/initialresources" "k8s.io/kubernetes/plugin/pkg/admission/limitranger" "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision" @@ -60,6 +61,7 @@ func registerAllAdmissionPlugins(plugins *admission.Plugins) { exec.Register(plugins) gc.Register(plugins) imagepolicy.Register(plugins) + initialization.Register(plugins) initialresources.Register(plugins) limitranger.Register(plugins) autoprovision.Register(plugins) diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index de6dc27f7a..b2c35f1e4a 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -93,7 +93,7 @@ const ( MinExternalEtcdVersion = "3.0.14" // DefaultAdmissionControl specifies the default admission control options that will be used - DefaultAdmissionControl = "NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds" + DefaultAdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds" ) var ( diff --git a/cmd/kubeadm/app/master/manifests_test.go b/cmd/kubeadm/app/master/manifests_test.go index a78a548128..de8cf267ad 100644 --- a/cmd/kubeadm/app/master/manifests_test.go +++ b/cmd/kubeadm/app/master/manifests_test.go @@ -522,7 +522,7 @@ func TestGetAPIServerCommand(t *testing.T) { expected: []string{ "kube-apiserver", "--insecure-port=0", - "--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", "--client-ca-file=" + testCertsDir + "/ca.crt", @@ -554,7 +554,7 @@ func TestGetAPIServerCommand(t *testing.T) { expected: []string{ "kube-apiserver", "--insecure-port=0", - "--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", "--client-ca-file=" + testCertsDir + "/ca.crt", @@ -587,7 +587,7 @@ func TestGetAPIServerCommand(t *testing.T) { expected: []string{ "kube-apiserver", "--insecure-port=0", - "--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", "--client-ca-file=" + testCertsDir + "/ca.crt", @@ -622,7 +622,7 @@ func TestGetAPIServerCommand(t *testing.T) { expected: []string{ "kube-apiserver", "--insecure-port=0", - "--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", + "--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", "--client-ca-file=" + testCertsDir + "/ca.crt", diff --git a/federation/cluster/common.sh b/federation/cluster/common.sh index ff355d6d5b..37fe14a6ec 100644 --- a/federation/cluster/common.sh +++ b/federation/cluster/common.sh @@ -258,7 +258,7 @@ function create-federation-api-objects { export FEDERATION_APISERVER_KEY_BASE64="${FEDERATION_APISERVER_KEY_BASE64}" # Enable the NamespaceLifecycle admission control by default. - export FEDERATION_ADMISSION_CONTROL="${FEDERATION_ADMISSION_CONTROL:-NamespaceLifecycle}" + export FEDERATION_ADMISSION_CONTROL="${FEDERATION_ADMISSION_CONTROL:-Initializers,NamespaceLifecycle}" for file in federation-etcd-pvc.yaml federation-apiserver-{deployment,secrets}.yaml federation-controller-manager-deployment.yaml; do echo "Creating manifest: ${file}" diff --git a/federation/cmd/federation-apiserver/app/BUILD b/federation/cmd/federation-apiserver/app/BUILD index 0966608dce..830442e65d 100644 --- a/federation/cmd/federation-apiserver/app/BUILD +++ b/federation/cmd/federation-apiserver/app/BUILD @@ -67,6 +67,7 @@ go_library( "//plugin/pkg/admission/admit:go_default_library", "//plugin/pkg/admission/deny:go_default_library", "//plugin/pkg/admission/gc:go_default_library", + "//plugin/pkg/admission/initialization:go_default_library", "//vendor/github.com/go-openapi/spec:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", diff --git a/federation/cmd/federation-apiserver/app/plugins.go b/federation/cmd/federation-apiserver/app/plugins.go index 61c5902eb1..168a0ef6e6 100644 --- a/federation/cmd/federation-apiserver/app/plugins.go +++ b/federation/cmd/federation-apiserver/app/plugins.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/plugin/pkg/admission/admit" "k8s.io/kubernetes/plugin/pkg/admission/deny" "k8s.io/kubernetes/plugin/pkg/admission/gc" + "k8s.io/kubernetes/plugin/pkg/admission/initialization" ) // registerAllAdmissionPlugins registers all admission plugins @@ -35,4 +36,5 @@ func registerAllAdmissionPlugins(plugins *admission.Plugins) { admit.Register(plugins) deny.Register(plugins) gc.Register(plugins) + initialization.Register(plugins) } diff --git a/federation/pkg/kubefed/init/init.go b/federation/pkg/kubefed/init/init.go index 13388216ef..d94a8abd3d 100644 --- a/federation/pkg/kubefed/init/init.go +++ b/federation/pkg/kubefed/init/init.go @@ -692,7 +692,7 @@ func createAPIServer(clientset client.Interface, namespace, name, federationName "--client-ca-file": "/etc/federation/apiserver/ca.crt", "--tls-cert-file": "/etc/federation/apiserver/server.crt", "--tls-private-key-file": "/etc/federation/apiserver/server.key", - "--admission-control": "NamespaceLifecycle", + "--admission-control": "Initializers,NamespaceLifecycle", } if advertiseAddress != "" { diff --git a/federation/pkg/kubefed/init/init_test.go b/federation/pkg/kubefed/init/init_test.go index 765bbad5ff..60a847f3f6 100644 --- a/federation/pkg/kubefed/init/init_test.go +++ b/federation/pkg/kubefed/init/init_test.go @@ -869,7 +869,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na fmt.Sprintf("--secure-port=%d", apiServerSecurePort), "--tls-cert-file=/etc/federation/apiserver/server.crt", "--tls-private-key-file=/etc/federation/apiserver/server.key", - "--admission-control=NamespaceLifecycle", + "--admission-control=Initializers,NamespaceLifecycle", fmt.Sprintf("--advertise-address=%s", address), } diff --git a/federation/registry/cluster/registry.go b/federation/registry/cluster/registry.go index 0cb919ea09..c1a54436f0 100644 --- a/federation/registry/cluster/registry.go +++ b/federation/registry/cluster/registry.go @@ -68,7 +68,7 @@ func (s *storage) GetCluster(ctx genericapirequest.Context, name string, options } func (s *storage) CreateCluster(ctx genericapirequest.Context, cluster *federation.Cluster) error { - _, err := s.Create(ctx, cluster) + _, err := s.Create(ctx, cluster, false) return err } diff --git a/federation/registry/cluster/strategy.go b/federation/registry/cluster/strategy.go index 4688bc7ecb..c278fd7390 100644 --- a/federation/registry/cluster/strategy.go +++ b/federation/registry/cluster/strategy.go @@ -48,12 +48,12 @@ func ClusterToSelectableFields(cluster *federation.Cluster) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { cluster, ok := obj.(*federation.Cluster) if !ok { - return nil, nil, fmt.Errorf("given object is not a cluster.") + return nil, nil, false, fmt.Errorf("given object is not a cluster.") } - return labels.Set(cluster.ObjectMeta.Labels), ClusterToSelectableFields(cluster), nil + return labels.Set(cluster.ObjectMeta.Labels), ClusterToSelectableFields(cluster), cluster.Initializers != nil, nil } func MatchCluster(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index f5f5554bba..d5fc8aac09 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -401,7 +401,7 @@ function start_apiserver { fi # Admission Controllers to invoke prior to persisting objects in cluster - ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},ResourceQuota,DefaultStorageClass,DefaultTolerationSeconds + ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},ResourceQuota,DefaultStorageClass,DefaultTolerationSeconds # This is the default dir and filename where the apiserver will generate a self-signed cert # which should be able to be used as the CA to verify itself diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index 9728367e00..a87a8dc088 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -34,7 +34,7 @@ function run_kube_apiserver() { kube::log::status "Starting kube-apiserver" # Admission Controllers to invoke prior to persisting objects in cluster - ADMISSION_CONTROL="NamespaceLifecycle,LimitRanger,ResourceQuota" + ADMISSION_CONTROL="Initializers,NamespaceLifecycle,LimitRanger,ResourceQuota" # Include RBAC (to exercise bootstrapping), and AlwaysAllow to allow all actions AUTHORIZATION_MODE="RBAC,AlwaysAllow" diff --git a/hack/make-rules/test-federation-cmd.sh b/hack/make-rules/test-federation-cmd.sh index 45e49a5d30..65e6f6594c 100755 --- a/hack/make-rules/test-federation-cmd.sh +++ b/hack/make-rules/test-federation-cmd.sh @@ -34,7 +34,7 @@ function run_federation_apiserver() { kube::log::status "Starting federation-apiserver" # Admission Controllers to invoke prior to persisting objects in cluster - ADMISSION_CONTROL="NamespaceLifecycle" + ADMISSION_CONTROL="Initializers,NamespaceLifecycle" "${KUBE_OUTPUT_HOSTBIN}/federation-apiserver" \ --insecure-port="${API_PORT}" \ diff --git a/pkg/master/thirdparty/thirdparty.go b/pkg/master/thirdparty/thirdparty.go index 50df6f763c..27788c8948 100644 --- a/pkg/master/thirdparty/thirdparty.go +++ b/pkg/master/thirdparty/thirdparty.go @@ -297,7 +297,7 @@ func (m *ThirdPartyResourceServer) migrateThirdPartyResourceData(gvk schema.Grou // Store CustomResource. obj := &unstructured.Unstructured{Object: objMap} createCtx := request.WithNamespace(ctx, obj.GetNamespace()) - if _, err := storage.Create(createCtx, obj); err != nil { + if _, err := storage.Create(createCtx, obj, false); err != nil { errs = append(errs, fmt.Errorf("can't create CustomResource for TPR data %q: %v", item.Name, err)) continue } diff --git a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/strategy.go b/pkg/registry/admissionregistration/externaladmissionhookconfiguration/strategy.go index eaa997206b..8876a40b42 100644 --- a/pkg/registry/admissionregistration/externaladmissionhookconfiguration/strategy.go +++ b/pkg/registry/admissionregistration/externaladmissionhookconfiguration/strategy.go @@ -106,15 +106,15 @@ func MatchExternalAdmissionHookConfiguration(label labels.Selector, field fields } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { ic, ok := obj.(*admissionregistration.ExternalAdmissionHookConfiguration) if !ok { - return nil, nil, fmt.Errorf("Given object is not a ExternalAdmissionHookConfiguration.") + return nil, nil, false, fmt.Errorf("Given object is not a ExternalAdmissionHookConfiguration.") } - return labels.Set(ic.ObjectMeta.Labels), ExternalAdmissionHookConfigurationToSelectableFields(ic), nil + return labels.Set(ic.ObjectMeta.Labels), ExternalAdmissionHookConfigurationToSelectableFields(ic), ic.Initializers != nil, nil } // ExternalAdmissionHookConfigurationToSelectableFields returns a field set that represents the object. func ExternalAdmissionHookConfigurationToSelectableFields(ic *admissionregistration.ExternalAdmissionHookConfiguration) fields.Set { - return generic.ObjectMetaFieldsSet(&ic.ObjectMeta, true) + return generic.ObjectMetaFieldsSet(&ic.ObjectMeta, false) } diff --git a/pkg/registry/admissionregistration/initializerconfiguration/strategy.go b/pkg/registry/admissionregistration/initializerconfiguration/strategy.go index c872c13bd9..6484e5c70b 100644 --- a/pkg/registry/admissionregistration/initializerconfiguration/strategy.go +++ b/pkg/registry/admissionregistration/initializerconfiguration/strategy.go @@ -106,15 +106,15 @@ func MatchInitializerConfiguration(label labels.Selector, field fields.Selector) } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { ic, ok := obj.(*admissionregistration.InitializerConfiguration) if !ok { - return nil, nil, fmt.Errorf("Given object is not a InitializerConfiguration.") + return nil, nil, false, fmt.Errorf("Given object is not a InitializerConfiguration.") } - return labels.Set(ic.ObjectMeta.Labels), InitializerConfigurationToSelectableFields(ic), nil + return labels.Set(ic.ObjectMeta.Labels), InitializerConfigurationToSelectableFields(ic), ic.ObjectMeta.Initializers != nil, nil } // InitializerConfigurationToSelectableFields returns a field set that represents the object. func InitializerConfigurationToSelectableFields(ic *admissionregistration.InitializerConfiguration) fields.Set { - return generic.ObjectMetaFieldsSet(&ic.ObjectMeta, true) + return generic.ObjectMetaFieldsSet(&ic.ObjectMeta, false) } diff --git a/pkg/registry/apps/controllerrevision/strategy.go b/pkg/registry/apps/controllerrevision/strategy.go index 86b01fe433..89cb78ab9e 100644 --- a/pkg/registry/apps/controllerrevision/strategy.go +++ b/pkg/registry/apps/controllerrevision/strategy.go @@ -90,12 +90,12 @@ func ControllerRevisionToSelectableFields(revision *apps.ControllerRevision) fie } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { history, ok := obj.(*apps.ControllerRevision) if !ok { - return nil, nil, errors.New("supplied object is not an ControllerRevision") + return nil, nil, false, errors.New("supplied object is not an ControllerRevision") } - return labels.Set(history.ObjectMeta.Labels), ControllerRevisionToSelectableFields(history), nil + return labels.Set(history.ObjectMeta.Labels), ControllerRevisionToSelectableFields(history), history.Initializers != nil, nil } // MatchControllerRevision returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/apps/controllerrevision/strategy_test.go b/pkg/registry/apps/controllerrevision/strategy_test.go index 97ce1be419..bba85db130 100644 --- a/pkg/registry/apps/controllerrevision/strategy_test.go +++ b/pkg/registry/apps/controllerrevision/strategy_test.go @@ -140,10 +140,13 @@ func TestControllerRevisionToSelectableFields(t *testing.T) { func TestGetAttrs(t *testing.T) { rev := newControllerRevision("validname", "validns", newObject(), 0) - labelSet, fieldSet, err := GetAttrs(rev) + labelSet, fieldSet, uninitialized, err := GetAttrs(rev) if err != nil { t.Fatal(err) } + if uninitialized { + t.Errorf("unexpected attrs") + } if fieldSet.Get("metadata.name") != rev.Name { t.Errorf("expeted %s found %s", rev.Name, fieldSet.Get("metadata.name")) } diff --git a/pkg/registry/apps/statefulset/storage/storage_test.go b/pkg/registry/apps/statefulset/storage/storage_test.go index 070e75781c..e7d5ac830f 100644 --- a/pkg/registry/apps/statefulset/storage/storage_test.go +++ b/pkg/registry/apps/statefulset/storage/storage_test.go @@ -42,7 +42,7 @@ func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) // createStatefulSet is a helper function that returns a StatefulSet with the updated resource version. func createStatefulSet(storage *REST, ps apps.StatefulSet, t *testing.T) (apps.StatefulSet, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), ps.Namespace) - obj, err := storage.Create(ctx, &ps) + obj, err := storage.Create(ctx, &ps, false) if err != nil { t.Errorf("Failed to create StatefulSet, %v", err) } diff --git a/pkg/registry/apps/statefulset/strategy.go b/pkg/registry/apps/statefulset/strategy.go index 3dc2c7031e..eda0b2a1fe 100644 --- a/pkg/registry/apps/statefulset/strategy.go +++ b/pkg/registry/apps/statefulset/strategy.go @@ -112,12 +112,12 @@ func StatefulSetToSelectableFields(statefulSet *apps.StatefulSet) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { statefulSet, ok := obj.(*apps.StatefulSet) if !ok { - return nil, nil, fmt.Errorf("given object is not an StatefulSet.") + return nil, nil, false, fmt.Errorf("given object is not an StatefulSet.") } - return labels.Set(statefulSet.ObjectMeta.Labels), StatefulSetToSelectableFields(statefulSet), nil + return labels.Set(statefulSet.ObjectMeta.Labels), StatefulSetToSelectableFields(statefulSet), statefulSet.Initializers != nil, nil } // MatchStatefulSet is the filter used by the generic etcd backend to watch events diff --git a/pkg/registry/authentication/tokenreview/storage.go b/pkg/registry/authentication/tokenreview/storage.go index 3c681ef9d2..0f3d73ac35 100644 --- a/pkg/registry/authentication/tokenreview/storage.go +++ b/pkg/registry/authentication/tokenreview/storage.go @@ -39,7 +39,7 @@ func (r *REST) New() runtime.Object { return &authentication.TokenReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { tokenReview, ok := obj.(*authentication.TokenReview) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("not a TokenReview: %#v", obj)) diff --git a/pkg/registry/authorization/localsubjectaccessreview/rest.go b/pkg/registry/authorization/localsubjectaccessreview/rest.go index 379a42b96b..d36f17582e 100644 --- a/pkg/registry/authorization/localsubjectaccessreview/rest.go +++ b/pkg/registry/authorization/localsubjectaccessreview/rest.go @@ -40,7 +40,7 @@ func (r *REST) New() runtime.Object { return &authorizationapi.LocalSubjectAccessReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { localSubjectAccessReview, ok := obj.(*authorizationapi.LocalSubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a LocaLocalSubjectAccessReview: %#v", obj)) diff --git a/pkg/registry/authorization/selfsubjectaccessreview/rest.go b/pkg/registry/authorization/selfsubjectaccessreview/rest.go index 94b4c06d72..4498b5f5a7 100644 --- a/pkg/registry/authorization/selfsubjectaccessreview/rest.go +++ b/pkg/registry/authorization/selfsubjectaccessreview/rest.go @@ -40,7 +40,7 @@ func (r *REST) New() runtime.Object { return &authorizationapi.SelfSubjectAccessReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { selfSAR, ok := obj.(*authorizationapi.SelfSubjectAccessReview) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectAccessReview: %#v", obj)) diff --git a/pkg/registry/authorization/subjectaccessreview/rest.go b/pkg/registry/authorization/subjectaccessreview/rest.go index 5b984b4c8b..1583a32ac0 100644 --- a/pkg/registry/authorization/subjectaccessreview/rest.go +++ b/pkg/registry/authorization/subjectaccessreview/rest.go @@ -40,7 +40,7 @@ func (r *REST) New() runtime.Object { return &authorizationapi.SubjectAccessReview{} } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a SubjectAccessReview: %#v", obj)) diff --git a/pkg/registry/authorization/subjectaccessreview/rest_test.go b/pkg/registry/authorization/subjectaccessreview/rest_test.go index bb485b97b9..b278538249 100644 --- a/pkg/registry/authorization/subjectaccessreview/rest_test.go +++ b/pkg/registry/authorization/subjectaccessreview/rest_test.go @@ -176,7 +176,7 @@ func TestCreate(t *testing.T) { } rest := NewREST(auth) - result, err := rest.Create(genericapirequest.NewContext(), &authorizationapi.SubjectAccessReview{Spec: tc.spec}) + result, err := rest.Create(genericapirequest.NewContext(), &authorizationapi.SubjectAccessReview{Spec: tc.spec}, false) if err != nil { if tc.expectedErr != "" { if !strings.Contains(err.Error(), tc.expectedErr) { diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go index b6ffc6bde4..e7b05126e1 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go @@ -91,12 +91,12 @@ func AutoscalerToSelectableFields(hpa *autoscaling.HorizontalPodAutoscaler) fiel } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { hpa, ok := obj.(*autoscaling.HorizontalPodAutoscaler) if !ok { - return nil, nil, fmt.Errorf("given object is not a horizontal pod autoscaler.") + return nil, nil, false, fmt.Errorf("given object is not a horizontal pod autoscaler.") } - return labels.Set(hpa.ObjectMeta.Labels), AutoscalerToSelectableFields(hpa), nil + return labels.Set(hpa.ObjectMeta.Labels), AutoscalerToSelectableFields(hpa), hpa.Initializers != nil, nil } func MatchAutoscaler(label labels.Selector, field fields.Selector) storage.SelectionPredicate { diff --git a/pkg/registry/batch/cronjob/strategy.go b/pkg/registry/batch/cronjob/strategy.go index c85eebb1e5..a78b9176d4 100644 --- a/pkg/registry/batch/cronjob/strategy.go +++ b/pkg/registry/batch/cronjob/strategy.go @@ -112,12 +112,12 @@ func CronJobToSelectableFields(cronJob *batch.CronJob) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { cronJob, ok := obj.(*batch.CronJob) if !ok { - return nil, nil, fmt.Errorf("Given object is not a scheduled job.") + return nil, nil, false, fmt.Errorf("given object is not a scheduled job.") } - return labels.Set(cronJob.ObjectMeta.Labels), CronJobToSelectableFields(cronJob), nil + return labels.Set(cronJob.ObjectMeta.Labels), CronJobToSelectableFields(cronJob), cronJob.Initializers != nil, nil } // MatchCronJob is the filter used by the generic etcd backend to route diff --git a/pkg/registry/batch/job/strategy.go b/pkg/registry/batch/job/strategy.go index 4bfdb3213f..d019270016 100644 --- a/pkg/registry/batch/job/strategy.go +++ b/pkg/registry/batch/job/strategy.go @@ -174,12 +174,12 @@ func JobToSelectableFields(job *batch.Job) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { job, ok := obj.(*batch.Job) if !ok { - return nil, nil, fmt.Errorf("Given object is not a job.") + return nil, nil, false, fmt.Errorf("given object is not a job.") } - return labels.Set(job.ObjectMeta.Labels), JobToSelectableFields(job), nil + return labels.Set(job.ObjectMeta.Labels), JobToSelectableFields(job), job.Initializers != nil, nil } // MatchJob is the filter used by the generic etcd backend to route diff --git a/pkg/registry/certificates/certificates/registry.go b/pkg/registry/certificates/certificates/registry.go index 05d3bb63a5..bff6e9dfb8 100644 --- a/pkg/registry/certificates/certificates/registry.go +++ b/pkg/registry/certificates/certificates/registry.go @@ -57,7 +57,7 @@ func (s *storage) ListCSRs(ctx genericapirequest.Context, options *metainternalv } func (s *storage) CreateCSR(ctx genericapirequest.Context, csr *certificates.CertificateSigningRequest) error { - _, err := s.Create(ctx, csr) + _, err := s.Create(ctx, csr, false) return err } diff --git a/pkg/registry/certificates/certificates/strategy.go b/pkg/registry/certificates/certificates/strategy.go index e8b625de73..15a3794e6f 100644 --- a/pkg/registry/certificates/certificates/strategy.go +++ b/pkg/registry/certificates/certificates/strategy.go @@ -178,12 +178,12 @@ func (csrApprovalStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, ol } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { sa, ok := obj.(*certificates.CertificateSigningRequest) if !ok { - return nil, nil, fmt.Errorf("not a CertificateSigningRequest") + return nil, nil, false, fmt.Errorf("not a CertificateSigningRequest") } - return labels.Set(sa.Labels), SelectableFields(sa), nil + return labels.Set(sa.Labels), SelectableFields(sa), sa.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/configmap/registry.go b/pkg/registry/core/configmap/registry.go index 1d4c9c6f62..c5e8fcab93 100644 --- a/pkg/registry/core/configmap/registry.go +++ b/pkg/registry/core/configmap/registry.go @@ -69,7 +69,7 @@ func (s *storage) GetConfigMap(ctx genericapirequest.Context, name string, optio } func (s *storage) CreateConfigMap(ctx genericapirequest.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) { - obj, err := s.Create(ctx, cfg) + obj, err := s.Create(ctx, cfg, false) if err != nil { return nil, err } diff --git a/pkg/registry/core/configmap/strategy.go b/pkg/registry/core/configmap/strategy.go index 2e45c2bb70..abb663882f 100644 --- a/pkg/registry/core/configmap/strategy.go +++ b/pkg/registry/core/configmap/strategy.go @@ -91,12 +91,12 @@ func ConfigMapToSelectableFields(cfg *api.ConfigMap) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { cfg, ok := obj.(*api.ConfigMap) if !ok { - return nil, nil, fmt.Errorf("given object is not a ConfigMap") + return nil, nil, false, fmt.Errorf("given object is not a ConfigMap") } - return labels.Set(cfg.ObjectMeta.Labels), ConfigMapToSelectableFields(cfg), nil + return labels.Set(cfg.ObjectMeta.Labels), ConfigMapToSelectableFields(cfg), cfg.Initializers != nil, nil } // MatchConfigMap returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/endpoint/strategy.go b/pkg/registry/core/endpoint/strategy.go index d095eca276..c50894b2e6 100644 --- a/pkg/registry/core/endpoint/strategy.go +++ b/pkg/registry/core/endpoint/strategy.go @@ -82,12 +82,12 @@ func (endpointsStrategy) AllowUnconditionalUpdate() bool { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { endpoints, ok := obj.(*api.Endpoints) if !ok { - return nil, nil, fmt.Errorf("invalid object type %#v", obj) + return nil, nil, false, fmt.Errorf("invalid object type %#v", obj) } - return endpoints.Labels, EndpointsToSelectableFields(endpoints), nil + return endpoints.Labels, EndpointsToSelectableFields(endpoints), endpoints.Initializers != nil, nil } // MatchEndpoints returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/event/strategy.go b/pkg/registry/core/event/strategy.go index 924dc795ee..77ea252b48 100644 --- a/pkg/registry/core/event/strategy.go +++ b/pkg/registry/core/event/strategy.go @@ -73,12 +73,12 @@ func (eventStrategy) AllowUnconditionalUpdate() bool { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { event, ok := obj.(*api.Event) if !ok { - return nil, nil, fmt.Errorf("not an event") + return nil, nil, false, fmt.Errorf("not an event") } - return labels.Set(event.Labels), EventToSelectableFields(event), nil + return labels.Set(event.Labels), EventToSelectableFields(event), event.Initializers != nil, nil } func MatchEvent(label labels.Selector, field fields.Selector) storage.SelectionPredicate { diff --git a/pkg/registry/core/limitrange/strategy.go b/pkg/registry/core/limitrange/strategy.go index 24bb511cbd..7f49d635a4 100644 --- a/pkg/registry/core/limitrange/strategy.go +++ b/pkg/registry/core/limitrange/strategy.go @@ -88,12 +88,12 @@ func (limitrangeStrategy) Export(genericapirequest.Context, runtime.Object, bool } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { lr, ok := obj.(*api.LimitRange) if !ok { - return nil, nil, fmt.Errorf("given object is not a limit range.") + return nil, nil, false, fmt.Errorf("given object is not a limit range.") } - return labels.Set(lr.ObjectMeta.Labels), LimitRangeToSelectableFields(lr), nil + return labels.Set(lr.ObjectMeta.Labels), LimitRangeToSelectableFields(lr), lr.Initializers != nil, nil } func MatchLimitRange(label labels.Selector, field fields.Selector) storage.SelectionPredicate { diff --git a/pkg/registry/core/namespace/registry.go b/pkg/registry/core/namespace/registry.go index dc5d422df5..e71282515a 100644 --- a/pkg/registry/core/namespace/registry.go +++ b/pkg/registry/core/namespace/registry.go @@ -67,7 +67,7 @@ func (s *storage) GetNamespace(ctx genericapirequest.Context, namespaceName stri } func (s *storage) CreateNamespace(ctx genericapirequest.Context, namespace *api.Namespace) error { - _, err := s.Create(ctx, namespace) + _, err := s.Create(ctx, namespace, false) return err } diff --git a/pkg/registry/core/namespace/storage/storage.go b/pkg/registry/core/namespace/storage/storage.go index 1d41ec1acc..f1c7ffc2df 100644 --- a/pkg/registry/core/namespace/storage/storage.go +++ b/pkg/registry/core/namespace/storage/storage.go @@ -92,8 +92,8 @@ func (r *REST) List(ctx genericapirequest.Context, options *metainternalversion. return r.store.List(ctx, options) } -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { - return r.store.Create(ctx, obj) +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { + return r.store.Create(ctx, obj, includeUninitialized) } func (r *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { diff --git a/pkg/registry/core/namespace/storage/storage_test.go b/pkg/registry/core/namespace/storage/storage_test.go index bae2ca9eaa..fdc57efe2b 100644 --- a/pkg/registry/core/namespace/storage/storage_test.go +++ b/pkg/registry/core/namespace/storage/storage_test.go @@ -67,7 +67,7 @@ func TestCreateSetsFields(t *testing.T) { defer storage.store.DestroyFunc() namespace := validNewNamespace() ctx := genericapirequest.NewContext() - _, err := storage.Create(ctx, namespace) + _, err := storage.Create(ctx, namespace, false) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/registry/core/namespace/strategy.go b/pkg/registry/core/namespace/strategy.go index 6826bdfcc1..e0d05b18f8 100644 --- a/pkg/registry/core/namespace/strategy.go +++ b/pkg/registry/core/namespace/strategy.go @@ -138,12 +138,12 @@ func (namespaceFinalizeStrategy) PrepareForUpdate(ctx genericapirequest.Context, } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { namespaceObj, ok := obj.(*api.Namespace) if !ok { - return nil, nil, fmt.Errorf("not a namespace") + return nil, nil, false, fmt.Errorf("not a namespace") } - return labels.Set(namespaceObj.Labels), NamespaceToSelectableFields(namespaceObj), nil + return labels.Set(namespaceObj.Labels), NamespaceToSelectableFields(namespaceObj), namespaceObj.Initializers != nil, nil } // MatchNamespace returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/node/registry.go b/pkg/registry/core/node/registry.go index 2fab505dbb..1e064fef51 100644 --- a/pkg/registry/core/node/registry.go +++ b/pkg/registry/core/node/registry.go @@ -56,7 +56,7 @@ func (s *storage) ListNodes(ctx genericapirequest.Context, options *metainternal } func (s *storage) CreateNode(ctx genericapirequest.Context, node *api.Node) error { - _, err := s.Create(ctx, node) + _, err := s.Create(ctx, node, false) return err } diff --git a/pkg/registry/core/node/strategy.go b/pkg/registry/core/node/strategy.go index e023364d75..16eea39e8e 100644 --- a/pkg/registry/core/node/strategy.go +++ b/pkg/registry/core/node/strategy.go @@ -148,12 +148,12 @@ func NodeToSelectableFields(node *api.Node) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { nodeObj, ok := obj.(*api.Node) if !ok { - return nil, nil, fmt.Errorf("not a node") + return nil, nil, false, fmt.Errorf("not a node") } - return labels.Set(nodeObj.ObjectMeta.Labels), NodeToSelectableFields(nodeObj), nil + return labels.Set(nodeObj.ObjectMeta.Labels), NodeToSelectableFields(nodeObj), nodeObj.Initializers != nil, nil } // MatchNode returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/persistentvolume/strategy.go b/pkg/registry/core/persistentvolume/strategy.go index 4d40ec5be8..33dfd905f7 100644 --- a/pkg/registry/core/persistentvolume/strategy.go +++ b/pkg/registry/core/persistentvolume/strategy.go @@ -102,12 +102,12 @@ func (persistentvolumeStatusStrategy) ValidateUpdate(ctx genericapirequest.Conte } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { persistentvolumeObj, ok := obj.(*api.PersistentVolume) if !ok { - return nil, nil, fmt.Errorf("not a persistentvolume") + return nil, nil, false, fmt.Errorf("not a persistentvolume") } - return labels.Set(persistentvolumeObj.Labels), PersistentVolumeToSelectableFields(persistentvolumeObj), nil + return labels.Set(persistentvolumeObj.Labels), PersistentVolumeToSelectableFields(persistentvolumeObj), persistentvolumeObj.Initializers != nil, nil } // MatchPersistentVolume returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/persistentvolumeclaim/strategy.go b/pkg/registry/core/persistentvolumeclaim/strategy.go index 386ed5c196..c653d505c8 100644 --- a/pkg/registry/core/persistentvolumeclaim/strategy.go +++ b/pkg/registry/core/persistentvolumeclaim/strategy.go @@ -98,12 +98,12 @@ func (persistentvolumeclaimStatusStrategy) ValidateUpdate(ctx genericapirequest. } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { persistentvolumeclaimObj, ok := obj.(*api.PersistentVolumeClaim) if !ok { - return nil, nil, fmt.Errorf("not a persistentvolumeclaim") + return nil, nil, false, fmt.Errorf("not a persistentvolumeclaim") } - return labels.Set(persistentvolumeclaimObj.Labels), PersistentVolumeClaimToSelectableFields(persistentvolumeclaimObj), nil + return labels.Set(persistentvolumeclaimObj.Labels), PersistentVolumeClaimToSelectableFields(persistentvolumeclaimObj), persistentvolumeclaimObj.Initializers != nil, nil } // MatchPersistentVolumeClaim returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/pod/storage/eviction.go b/pkg/registry/core/pod/storage/eviction.go index 214680cce0..2c2d522bb9 100644 --- a/pkg/registry/core/pod/storage/eviction.go +++ b/pkg/registry/core/pod/storage/eviction.go @@ -71,7 +71,7 @@ func (r *EvictionREST) New() runtime.Object { } // Create attempts to create a new eviction. That is, it tries to evict a pod. -func (r *EvictionREST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (r *EvictionREST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { eviction := obj.(*policy.Eviction) obj, err := r.store.Get(ctx, eviction.Name, &metav1.GetOptions{}) diff --git a/pkg/registry/core/pod/storage/storage.go b/pkg/registry/core/pod/storage/storage.go index d4107fff03..0e1a7d61be 100644 --- a/pkg/registry/core/pod/storage/storage.go +++ b/pkg/registry/core/pod/storage/storage.go @@ -130,7 +130,7 @@ func (r *BindingREST) New() runtime.Object { var _ = rest.Creater(&BindingREST{}) // Create ensures a pod is bound to a specific host. -func (r *BindingREST) Create(ctx genericapirequest.Context, obj runtime.Object) (out runtime.Object, err error) { +func (r *BindingREST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (out runtime.Object, err error) { binding := obj.(*api.Binding) // TODO: move me to a binding strategy diff --git a/pkg/registry/core/pod/storage/storage_test.go b/pkg/registry/core/pod/storage/storage_test.go index 726d19afa0..d096a1ce4b 100644 --- a/pkg/registry/core/pod/storage/storage_test.go +++ b/pkg/registry/core/pod/storage/storage_test.go @@ -185,7 +185,7 @@ func TestIgnoreDeleteNotFound(t *testing.T) { } // create pod - _, err = registry.Create(testContext, pod) + _, err = registry.Create(testContext, pod, false) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -222,7 +222,7 @@ func TestCreateSetsFields(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() pod := validNewPod() - _, err := storage.Create(genericapirequest.NewDefaultContext(), pod) + _, err := storage.Create(genericapirequest.NewDefaultContext(), pod, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -486,7 +486,7 @@ func TestEtcdCreate(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod()) + _, err := storage.Create(ctx, validNewPod(), false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -495,7 +495,7 @@ func TestEtcdCreate(t *testing.T) { _, err = bindingStorage.Create(ctx, &api.Binding{ ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, Target: api.ObjectReference{Name: "machine"}, - }) + }, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -521,7 +521,7 @@ func TestEtcdCreateBindingNoPod(t *testing.T) { _, err := bindingStorage.Create(ctx, &api.Binding{ ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, Target: api.ObjectReference{Name: "machine"}, - }) + }, false) if err == nil { t.Fatalf("Expected not-found-error but got nothing") } @@ -544,7 +544,7 @@ func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { defer storage.Store.DestroyFunc() pod := validNewPod() pod.Namespace = "" - _, err := storage.Create(genericapirequest.NewContext(), pod) + _, err := storage.Create(genericapirequest.NewContext(), pod, false) // Accept "namespace" or "Namespace". if err == nil || !strings.Contains(err.Error(), "amespace") { t.Fatalf("expected error that namespace was missing from context, got: %v", err) @@ -556,7 +556,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod()) + _, err := storage.Create(ctx, validNewPod(), false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -569,7 +569,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) { Annotations: map[string]string{"label1": "value1"}, }, Target: api.ObjectReference{Name: "machine"}, - }) + }, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -591,7 +591,7 @@ func TestEtcdCreateWithConflict(t *testing.T) { defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod()) + _, err := storage.Create(ctx, validNewPod(), false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -605,12 +605,12 @@ func TestEtcdCreateWithConflict(t *testing.T) { }, Target: api.ObjectReference{Name: "machine"}, } - _, err = bindingStorage.Create(ctx, &binding) + _, err = bindingStorage.Create(ctx, &binding, false) if err != nil { t.Fatalf("unexpected error: %v", err) } - _, err = bindingStorage.Create(ctx, &binding) + _, err = bindingStorage.Create(ctx, &binding, false) if err == nil || !errors.IsConflict(err) { t.Fatalf("expected resource conflict error, not: %v", err) } @@ -621,7 +621,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) { defer server.Terminate(t) defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - _, err := storage.Create(ctx, validNewPod()) + _, err := storage.Create(ctx, validNewPod(), false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -630,7 +630,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) { _, err = bindingStorage.Create(ctx, &api.Binding{ ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, Target: api.ObjectReference{Name: "machine"}, - }) + }, false) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -680,10 +680,10 @@ func TestEtcdCreateBinding(t *testing.T) { for k, test := range testCases { storage, bindingStorage, _, server := newStorage(t) - if _, err := storage.Create(ctx, validNewPod()); err != nil { + if _, err := storage.Create(ctx, validNewPod(), false); err != nil { t.Fatalf("%s: unexpected error: %v", k, err) } - if _, err := bindingStorage.Create(ctx, &test.binding); !test.errOK(err) { + if _, err := bindingStorage.Create(ctx, &test.binding, false); !test.errOK(err) { t.Errorf("%s: unexpected error: %v", k, err) } else if err == nil { // If bind succeeded, verify Host field in pod's Spec. @@ -705,7 +705,7 @@ func TestEtcdUpdateNotScheduled(t *testing.T) { defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() - if _, err := storage.Create(ctx, validNewPod()); err != nil { + if _, err := storage.Create(ctx, validNewPod(), false); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index 60fd93fed6..00a3c4f991 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -165,12 +165,12 @@ func (podStatusStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod, ok := obj.(*api.Pod) if !ok { - return nil, nil, fmt.Errorf("not a pod") + return nil, nil, false, fmt.Errorf("not a pod") } - return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil + return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), pod.Initializers != nil, nil } // MatchPod returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/podtemplate/strategy.go b/pkg/registry/core/podtemplate/strategy.go index 5f925daece..87012c541c 100644 --- a/pkg/registry/core/podtemplate/strategy.go +++ b/pkg/registry/core/podtemplate/strategy.go @@ -89,12 +89,12 @@ func PodTemplateToSelectableFields(podTemplate *api.PodTemplate) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pt, ok := obj.(*api.PodTemplate) if !ok { - return nil, nil, fmt.Errorf("given object is not a pod template.") + return nil, nil, false, fmt.Errorf("given object is not a pod template.") } - return labels.Set(pt.ObjectMeta.Labels), PodTemplateToSelectableFields(pt), nil + return labels.Set(pt.ObjectMeta.Labels), PodTemplateToSelectableFields(pt), pt.Initializers != nil, nil } func MatchPodTemplate(label labels.Selector, field fields.Selector) storage.SelectionPredicate { diff --git a/pkg/registry/core/replicationcontroller/registry.go b/pkg/registry/core/replicationcontroller/registry.go index 02eff75fe7..df0d7418d0 100644 --- a/pkg/registry/core/replicationcontroller/registry.go +++ b/pkg/registry/core/replicationcontroller/registry.go @@ -74,7 +74,7 @@ func (s *storage) GetController(ctx genericapirequest.Context, controllerID stri } func (s *storage) CreateController(ctx genericapirequest.Context, controller *api.ReplicationController) (*api.ReplicationController, error) { - obj, err := s.Create(ctx, controller) + obj, err := s.Create(ctx, controller, false) if err != nil { return nil, err } diff --git a/pkg/registry/core/replicationcontroller/storage/storage_test.go b/pkg/registry/core/replicationcontroller/storage/storage_test.go index ea102580a3..258676fdc4 100644 --- a/pkg/registry/core/replicationcontroller/storage/storage_test.go +++ b/pkg/registry/core/replicationcontroller/storage/storage_test.go @@ -55,7 +55,7 @@ func newStorage(t *testing.T) (ControllerStorage, *etcdtesting.EtcdTestServer) { // createController is a helper function that returns a controller with the updated resource version. func createController(storage *REST, rc api.ReplicationController, t *testing.T) (api.ReplicationController, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), rc.Namespace) - obj, err := storage.Create(ctx, &rc) + obj, err := storage.Create(ctx, &rc, false) if err != nil { t.Errorf("Failed to create controller, %v", err) } diff --git a/pkg/registry/core/replicationcontroller/strategy.go b/pkg/registry/core/replicationcontroller/strategy.go index d3ff54cf8b..fd19c5d49a 100644 --- a/pkg/registry/core/replicationcontroller/strategy.go +++ b/pkg/registry/core/replicationcontroller/strategy.go @@ -145,12 +145,12 @@ func ControllerToSelectableFields(controller *api.ReplicationController) fields. } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { rc, ok := obj.(*api.ReplicationController) if !ok { - return nil, nil, fmt.Errorf("Given object is not a replication controller.") + return nil, nil, false, fmt.Errorf("given object is not a replication controller.") } - return labels.Set(rc.ObjectMeta.Labels), ControllerToSelectableFields(rc), nil + return labels.Set(rc.ObjectMeta.Labels), ControllerToSelectableFields(rc), rc.Initializers != nil, nil } // MatchController is the filter used by the generic etcd backend to route diff --git a/pkg/registry/core/resourcequota/storage/storage_test.go b/pkg/registry/core/resourcequota/storage/storage_test.go index d5c1778131..d9a4765932 100644 --- a/pkg/registry/core/resourcequota/storage/storage_test.go +++ b/pkg/registry/core/resourcequota/storage/storage_test.go @@ -87,7 +87,7 @@ func TestCreateSetsFields(t *testing.T) { defer storage.Store.DestroyFunc() ctx := genericapirequest.NewDefaultContext() resourcequota := validNewResourceQuota() - _, err := storage.Create(genericapirequest.NewDefaultContext(), resourcequota) + _, err := storage.Create(genericapirequest.NewDefaultContext(), resourcequota, false) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/registry/core/resourcequota/strategy.go b/pkg/registry/core/resourcequota/strategy.go index 252567d625..6211e8f4bb 100644 --- a/pkg/registry/core/resourcequota/strategy.go +++ b/pkg/registry/core/resourcequota/strategy.go @@ -101,12 +101,12 @@ func (resourcequotaStatusStrategy) ValidateUpdate(ctx genericapirequest.Context, } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { resourcequotaObj, ok := obj.(*api.ResourceQuota) if !ok { - return nil, nil, fmt.Errorf("not a resourcequota") + return nil, nil, false, fmt.Errorf("not a resourcequota") } - return labels.Set(resourcequotaObj.Labels), ResourceQuotaToSelectableFields(resourcequotaObj), nil + return labels.Set(resourcequotaObj.Labels), ResourceQuotaToSelectableFields(resourcequotaObj), resourcequotaObj.Initializers != nil, nil } // MatchResourceQuota returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/secret/registry.go b/pkg/registry/core/secret/registry.go index 21ecbc6d09..cfe8d98569 100644 --- a/pkg/registry/core/secret/registry.go +++ b/pkg/registry/core/secret/registry.go @@ -67,7 +67,7 @@ func (s *storage) GetSecret(ctx genericapirequest.Context, name string, options } func (s *storage) CreateSecret(ctx genericapirequest.Context, secret *api.Secret) (*api.Secret, error) { - obj, err := s.Create(ctx, secret) + obj, err := s.Create(ctx, secret, false) return obj.(*api.Secret), err } diff --git a/pkg/registry/core/secret/strategy.go b/pkg/registry/core/secret/strategy.go index 9e0236b3b1..59150c6402 100644 --- a/pkg/registry/core/secret/strategy.go +++ b/pkg/registry/core/secret/strategy.go @@ -97,12 +97,12 @@ func (s strategy) Export(ctx genericapirequest.Context, obj runtime.Object, exac } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { secret, ok := obj.(*api.Secret) if !ok { - return nil, nil, fmt.Errorf("not a secret") + return nil, nil, false, fmt.Errorf("not a secret") } - return labels.Set(secret.Labels), SelectableFields(secret), nil + return labels.Set(secret.Labels), SelectableFields(secret), secret.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/core/service/registry.go b/pkg/registry/core/service/registry.go index e37afb5007..0b7dad98bb 100644 --- a/pkg/registry/core/service/registry.go +++ b/pkg/registry/core/service/registry.go @@ -58,7 +58,7 @@ func (s *storage) ListServices(ctx genericapirequest.Context, options *metainter } func (s *storage) CreateService(ctx genericapirequest.Context, svc *api.Service) (*api.Service, error) { - obj, err := s.Create(ctx, svc) + obj, err := s.Create(ctx, svc, false) if err != nil { return nil, err } diff --git a/pkg/registry/core/service/rest.go b/pkg/registry/core/service/rest.go index eadf305031..a369708579 100644 --- a/pkg/registry/core/service/rest.go +++ b/pkg/registry/core/service/rest.go @@ -77,7 +77,8 @@ func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipall } } -func (rs *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +// TODO: implement includeUninitialized by refactoring this to move to store +func (rs *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { service := obj.(*api.Service) if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { diff --git a/pkg/registry/core/service/rest_test.go b/pkg/registry/core/service/rest_test.go index ebd863c373..511be53785 100644 --- a/pkg/registry/core/service/rest_test.go +++ b/pkg/registry/core/service/rest_test.go @@ -101,7 +101,7 @@ func TestServiceRegistryCreate(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -225,7 +225,7 @@ func TestServiceRegistryCreateMultiNodePortsService(t *testing.T) { ctx := genericapirequest.NewDefaultContext() for _, test := range testCases { - created_svc, err := storage.Create(ctx, test.svc) + created_svc, err := storage.Create(ctx, test.svc, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -296,7 +296,7 @@ func TestServiceStorageValidatesCreate(t *testing.T) { } ctx := genericapirequest.NewDefaultContext() for _, failureCase := range failureCases { - c, err := storage.Create(ctx, &failureCase) + c, err := storage.Create(ctx, &failureCase, false) if c != nil { t.Errorf("Expected nil object") } @@ -425,7 +425,7 @@ func TestServiceRegistryExternalService(t *testing.T) { }}, }, } - _, err := storage.Create(ctx, svc) + _, err := storage.Create(ctx, svc, false) if err != nil { t.Errorf("Failed to create service: %#v", err) } @@ -500,7 +500,7 @@ func TestServiceRegistryUpdateExternalService(t *testing.T) { }}, }, } - if _, err := storage.Create(ctx, svc1); err != nil { + if _, err := storage.Create(ctx, svc1, false); err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -543,7 +543,7 @@ func TestServiceRegistryUpdateMultiPortExternalService(t *testing.T) { }}, }, } - if _, err := storage.Create(ctx, svc1); err != nil { + if _, err := storage.Create(ctx, svc1, false); err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -740,7 +740,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc1, _ := storage.Create(ctx, svc1) + created_svc1, _ := storage.Create(ctx, svc1, false) created_service_1 := created_svc1.(*api.Service) if created_service_1.Name != "foo" { t.Errorf("Expected foo, but got %v", created_service_1.Name) @@ -762,7 +762,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) { }}, }} ctx = genericapirequest.NewDefaultContext() - created_svc2, _ := storage.Create(ctx, svc2) + created_svc2, _ := storage.Create(ctx, svc2, false) created_service_2 := created_svc2.(*api.Service) if created_service_2.Name != "bar" { t.Errorf("Expected bar, but got %v", created_service_2.Name) @@ -795,7 +795,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) { }, } ctx = genericapirequest.NewDefaultContext() - created_svc3, err := storage.Create(ctx, svc3) + created_svc3, err := storage.Create(ctx, svc3, false) if err != nil { t.Fatal(err) } @@ -822,7 +822,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc1, _ := storage.Create(ctx, svc1) + created_svc1, _ := storage.Create(ctx, svc1, false) created_service_1 := created_svc1.(*api.Service) if created_service_1.Name != "foo" { t.Errorf("Expected foo, but got %v", created_service_1.Name) @@ -850,7 +850,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) { }, } ctx = genericapirequest.NewDefaultContext() - created_svc2, _ := storage.Create(ctx, svc2) + created_svc2, _ := storage.Create(ctx, svc2, false) created_service_2 := created_svc2.(*api.Service) if created_service_2.Name != "bar" { t.Errorf("Expected bar, but got %v", created_service_2.Name) @@ -877,7 +877,7 @@ func TestServiceRegistryIPUpdate(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc, _ := storage.Create(ctx, svc) + created_svc, _ := storage.Create(ctx, svc, false) created_service := created_svc.(*api.Service) if created_service.Spec.Ports[0].Port != 6502 { t.Errorf("Expected port 6502, but got %v", created_service.Spec.Ports[0].Port) @@ -931,7 +931,7 @@ func TestServiceRegistryIPLoadBalancer(t *testing.T) { }, } ctx := genericapirequest.NewDefaultContext() - created_svc, _ := storage.Create(ctx, svc) + created_svc, _ := storage.Create(ctx, svc, false) created_service := created_svc.(*api.Service) if created_service.Spec.Ports[0].Port != 6502 { t.Errorf("Expected port 6502, but got %v", created_service.Spec.Ports[0].Port) @@ -985,7 +985,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortAllocation(t *testing. ExternalTrafficPolicy: api.ServiceExternalTrafficPolicyTypeLocal, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } @@ -1023,7 +1023,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortAllocationBeta(t *test }}, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } @@ -1059,7 +1059,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortUserAllocation(t *test HealthCheckNodePort: randomNodePort, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { t.Fatalf("Unexpected failure creating service :%v", err) } @@ -1101,7 +1101,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortUserAllocationBeta(t * }}, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { t.Fatalf("Unexpected failure creating service :%v", err) } @@ -1137,7 +1137,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortNegative(t *testing.T) HealthCheckNodePort: int32(-1), }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { return } @@ -1167,7 +1167,7 @@ func TestServiceRegistryExternalTrafficHealthCheckNodePortNegativeBeta(t *testin }}, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { return } @@ -1192,7 +1192,7 @@ func TestServiceRegistryExternalTrafficGlobal(t *testing.T) { ExternalTrafficPolicy: api.ServiceExternalTrafficPolicyTypeGlobal, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } @@ -1229,7 +1229,7 @@ func TestServiceRegistryExternalTrafficGlobalBeta(t *testing.T) { }}, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } @@ -1265,7 +1265,7 @@ func TestServiceRegistryExternalTrafficAnnotationClusterIP(t *testing.T) { }}, }, } - created_svc, err := storage.Create(ctx, svc) + created_svc, err := storage.Create(ctx, svc, false) if created_svc == nil || err != nil { t.Errorf("Unexpected failure creating service %v", err) } diff --git a/pkg/registry/core/service/strategy.go b/pkg/registry/core/service/strategy.go index e01920ece6..7736f6c2cc 100644 --- a/pkg/registry/core/service/strategy.go +++ b/pkg/registry/core/service/strategy.go @@ -104,12 +104,12 @@ func (svcStrategy) Export(ctx genericapirequest.Context, obj runtime.Object, exa } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { service, ok := obj.(*api.Service) if !ok { - return nil, nil, fmt.Errorf("Given object is not a service") + return nil, nil, false, fmt.Errorf("given object is not a service") } - return labels.Set(service.ObjectMeta.Labels), ServiceToSelectableFields(service), nil + return labels.Set(service.ObjectMeta.Labels), ServiceToSelectableFields(service), service.Initializers != nil, nil } func MatchServices(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { diff --git a/pkg/registry/core/serviceaccount/registry.go b/pkg/registry/core/serviceaccount/registry.go index 0a733d5a80..4fa100244f 100644 --- a/pkg/registry/core/serviceaccount/registry.go +++ b/pkg/registry/core/serviceaccount/registry.go @@ -67,7 +67,7 @@ func (s *storage) GetServiceAccount(ctx genericapirequest.Context, name string, } func (s *storage) CreateServiceAccount(ctx genericapirequest.Context, serviceAccount *api.ServiceAccount) error { - _, err := s.Create(ctx, serviceAccount) + _, err := s.Create(ctx, serviceAccount, false) return err } diff --git a/pkg/registry/core/serviceaccount/strategy.go b/pkg/registry/core/serviceaccount/strategy.go index 40a3f7d94d..2ff37ecd94 100644 --- a/pkg/registry/core/serviceaccount/strategy.go +++ b/pkg/registry/core/serviceaccount/strategy.go @@ -80,12 +80,12 @@ func (strategy) AllowUnconditionalUpdate() bool { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { sa, ok := obj.(*api.ServiceAccount) if !ok { - return nil, nil, fmt.Errorf("not a serviceaccount") + return nil, nil, false, fmt.Errorf("not a serviceaccount") } - return labels.Set(sa.Labels), SelectableFields(sa), nil + return labels.Set(sa.Labels), SelectableFields(sa), sa.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/extensions/daemonset/strategy.go b/pkg/registry/extensions/daemonset/strategy.go index cc06e0a9cb..80e03dc166 100644 --- a/pkg/registry/extensions/daemonset/strategy.go +++ b/pkg/registry/extensions/daemonset/strategy.go @@ -131,12 +131,12 @@ func DaemonSetToSelectableFields(daemon *extensions.DaemonSet) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { ds, ok := obj.(*extensions.DaemonSet) if !ok { - return nil, nil, fmt.Errorf("given object is not a ds.") + return nil, nil, false, fmt.Errorf("given object is not a ds.") } - return labels.Set(ds.ObjectMeta.Labels), DaemonSetToSelectableFields(ds), nil + return labels.Set(ds.ObjectMeta.Labels), DaemonSetToSelectableFields(ds), ds.Initializers != nil, nil } // MatchSetDaemon is the filter used by the generic etcd backend to route diff --git a/pkg/registry/extensions/deployment/registry.go b/pkg/registry/extensions/deployment/registry.go index 45d9522400..88f147e2aa 100644 --- a/pkg/registry/extensions/deployment/registry.go +++ b/pkg/registry/extensions/deployment/registry.go @@ -66,7 +66,7 @@ func (s *storage) GetDeployment(ctx genericapirequest.Context, deploymentID stri } func (s *storage) CreateDeployment(ctx genericapirequest.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) { - obj, err := s.Create(ctx, deployment) + obj, err := s.Create(ctx, deployment, false) if err != nil { return nil, err } diff --git a/pkg/registry/extensions/deployment/storage/storage.go b/pkg/registry/extensions/deployment/storage/storage.go index 2b65ec9aa9..701ccf1494 100644 --- a/pkg/registry/extensions/deployment/storage/storage.go +++ b/pkg/registry/extensions/deployment/storage/storage.go @@ -123,7 +123,7 @@ func (r *RollbackREST) New() runtime.Object { var _ = rest.Creater(&RollbackREST{}) -func (r *RollbackREST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (r *RollbackREST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { rollback, ok := obj.(*extensions.DeploymentRollback) if !ok { return nil, errors.NewBadRequest(fmt.Sprintf("not a DeploymentRollback: %#v", obj)) diff --git a/pkg/registry/extensions/deployment/storage/storage_test.go b/pkg/registry/extensions/deployment/storage/storage_test.go index 67042f546f..b3c14f69f5 100644 --- a/pkg/registry/extensions/deployment/storage/storage_test.go +++ b/pkg/registry/extensions/deployment/storage/storage_test.go @@ -340,10 +340,10 @@ func TestEtcdCreateDeploymentRollback(t *testing.T) { storage, server := newStorage(t) rollbackStorage := storage.Rollback - if _, err := storage.Deployment.Create(ctx, validNewDeployment()); err != nil { + if _, err := storage.Deployment.Create(ctx, validNewDeployment(), false); err != nil { t.Fatalf("%s: unexpected error: %v", k, err) } - if _, err := rollbackStorage.Create(ctx, &test.rollback); !test.errOK(err) { + if _, err := rollbackStorage.Create(ctx, &test.rollback, false); !test.errOK(err) { t.Errorf("%s: unexpected error: %v", k, err) } else if err == nil { // If rollback succeeded, verify Rollback field of deployment @@ -372,7 +372,7 @@ func TestEtcdCreateDeploymentRollbackNoDeployment(t *testing.T) { Name: name, UpdatedAnnotations: map[string]string{}, RollbackTo: extensions.RollbackConfig{Revision: 1}, - }) + }, false) if err == nil { t.Fatalf("Expected not-found-error but got nothing") } diff --git a/pkg/registry/extensions/deployment/strategy.go b/pkg/registry/extensions/deployment/strategy.go index 2dd0574ff8..819ca5e965 100644 --- a/pkg/registry/extensions/deployment/strategy.go +++ b/pkg/registry/extensions/deployment/strategy.go @@ -126,12 +126,12 @@ func DeploymentToSelectableFields(deployment *extensions.Deployment) fields.Set } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { deployment, ok := obj.(*extensions.Deployment) if !ok { - return nil, nil, fmt.Errorf("given object is not a deployment.") + return nil, nil, false, fmt.Errorf("given object is not a deployment.") } - return labels.Set(deployment.ObjectMeta.Labels), DeploymentToSelectableFields(deployment), nil + return labels.Set(deployment.ObjectMeta.Labels), DeploymentToSelectableFields(deployment), deployment.Initializers != nil, nil } // MatchDeployment is the filter used by the generic etcd backend to route diff --git a/pkg/registry/extensions/ingress/strategy.go b/pkg/registry/extensions/ingress/strategy.go index 2f61923761..add1e0a327 100644 --- a/pkg/registry/extensions/ingress/strategy.go +++ b/pkg/registry/extensions/ingress/strategy.go @@ -106,12 +106,12 @@ func IngressToSelectableFields(ingress *extensions.Ingress) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { ingress, ok := obj.(*extensions.Ingress) if !ok { - return nil, nil, fmt.Errorf("Given object is not an Ingress.") + return nil, nil, false, fmt.Errorf("given object is not an Ingress.") } - return labels.Set(ingress.ObjectMeta.Labels), IngressToSelectableFields(ingress), nil + return labels.Set(ingress.ObjectMeta.Labels), IngressToSelectableFields(ingress), ingress.Initializers != nil, nil } // MatchIngress is the filter used by the generic etcd backend to ingress diff --git a/pkg/registry/extensions/networkpolicy/storage/storage_test.go b/pkg/registry/extensions/networkpolicy/storage/storage_test.go index 65e03392ab..2850ea2014 100644 --- a/pkg/registry/extensions/networkpolicy/storage/storage_test.go +++ b/pkg/registry/extensions/networkpolicy/storage/storage_test.go @@ -45,7 +45,7 @@ func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { // createNetworkPolicy is a helper function that returns a NetworkPolicy with the updated resource version. func createNetworkPolicy(storage *REST, np extensions.NetworkPolicy, t *testing.T) (extensions.NetworkPolicy, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), np.Namespace) - obj, err := storage.Create(ctx, &np) + obj, err := storage.Create(ctx, &np, false) if err != nil { t.Errorf("Failed to create NetworkPolicy, %v", err) } diff --git a/pkg/registry/extensions/networkpolicy/strategy.go b/pkg/registry/extensions/networkpolicy/strategy.go index 327262f73d..63eb28b719 100644 --- a/pkg/registry/extensions/networkpolicy/strategy.go +++ b/pkg/registry/extensions/networkpolicy/strategy.go @@ -99,12 +99,12 @@ func NetworkPolicyToSelectableFields(networkPolicy *extensions.NetworkPolicy) fi } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { networkPolicy, ok := obj.(*extensions.NetworkPolicy) if !ok { - return nil, nil, fmt.Errorf("given object is not a NetworkPolicy.") + return nil, nil, false, fmt.Errorf("given object is not a NetworkPolicy.") } - return labels.Set(networkPolicy.ObjectMeta.Labels), NetworkPolicyToSelectableFields(networkPolicy), nil + return labels.Set(networkPolicy.ObjectMeta.Labels), NetworkPolicyToSelectableFields(networkPolicy), networkPolicy.Initializers != nil, nil } // MatchNetworkPolicy is the filter used by the generic etcd backend to watch events diff --git a/pkg/registry/extensions/podsecuritypolicy/strategy.go b/pkg/registry/extensions/podsecuritypolicy/strategy.go index 1f3d3ddb01..d9213619da 100644 --- a/pkg/registry/extensions/podsecuritypolicy/strategy.go +++ b/pkg/registry/extensions/podsecuritypolicy/strategy.go @@ -77,12 +77,12 @@ func (strategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.O } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { psp, ok := obj.(*extensions.PodSecurityPolicy) if !ok { - return nil, nil, fmt.Errorf("given object is not a pod security policy.") + return nil, nil, false, fmt.Errorf("given object is not a pod security policy.") } - return labels.Set(psp.ObjectMeta.Labels), PodSecurityPolicyToSelectableFields(psp), nil + return labels.Set(psp.ObjectMeta.Labels), PodSecurityPolicyToSelectableFields(psp), psp.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/extensions/replicaset/registry.go b/pkg/registry/extensions/replicaset/registry.go index e33cbd075b..87e01d8e0f 100644 --- a/pkg/registry/extensions/replicaset/registry.go +++ b/pkg/registry/extensions/replicaset/registry.go @@ -75,7 +75,7 @@ func (s *storage) GetReplicaSet(ctx genericapirequest.Context, replicaSetID stri } func (s *storage) CreateReplicaSet(ctx genericapirequest.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { - obj, err := s.Create(ctx, replicaSet) + obj, err := s.Create(ctx, replicaSet, false) if err != nil { return nil, err } diff --git a/pkg/registry/extensions/replicaset/storage/storage_test.go b/pkg/registry/extensions/replicaset/storage/storage_test.go index a5f981c2f0..fc89666d5a 100644 --- a/pkg/registry/extensions/replicaset/storage/storage_test.go +++ b/pkg/registry/extensions/replicaset/storage/storage_test.go @@ -47,7 +47,7 @@ func newStorage(t *testing.T) (*ReplicaSetStorage, *etcdtesting.EtcdTestServer) // createReplicaSet is a helper function that returns a ReplicaSet with the updated resource version. func createReplicaSet(storage *REST, rs extensions.ReplicaSet, t *testing.T) (extensions.ReplicaSet, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), rs.Namespace) - obj, err := storage.Create(ctx, &rs) + obj, err := storage.Create(ctx, &rs, false) if err != nil { t.Errorf("Failed to create ReplicaSet, %v", err) } diff --git a/pkg/registry/extensions/replicaset/strategy.go b/pkg/registry/extensions/replicaset/strategy.go index 260bafedd8..e7bf7d5f37 100644 --- a/pkg/registry/extensions/replicaset/strategy.go +++ b/pkg/registry/extensions/replicaset/strategy.go @@ -122,12 +122,12 @@ func ReplicaSetToSelectableFields(rs *extensions.ReplicaSet) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { rs, ok := obj.(*extensions.ReplicaSet) if !ok { - return nil, nil, fmt.Errorf("Given object is not a ReplicaSet.") + return nil, nil, false, fmt.Errorf("given object is not a ReplicaSet.") } - return labels.Set(rs.ObjectMeta.Labels), ReplicaSetToSelectableFields(rs), nil + return labels.Set(rs.ObjectMeta.Labels), ReplicaSetToSelectableFields(rs), rs.Initializers != nil, nil } // MatchReplicaSet is the filter used by the generic etcd backend to route diff --git a/pkg/registry/extensions/thirdpartyresource/strategy.go b/pkg/registry/extensions/thirdpartyresource/strategy.go index 52a55d1bf4..baf13844c8 100644 --- a/pkg/registry/extensions/thirdpartyresource/strategy.go +++ b/pkg/registry/extensions/thirdpartyresource/strategy.go @@ -79,12 +79,12 @@ func (strategy) AllowUnconditionalUpdate() bool { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { tpr, ok := obj.(*extensions.ThirdPartyResource) if !ok { - return nil, nil, fmt.Errorf("not a ThirdPartyResource") + return nil, nil, false, fmt.Errorf("not a ThirdPartyResource") } - return labels.Set(tpr.Labels), SelectableFields(tpr), nil + return labels.Set(tpr.Labels), SelectableFields(tpr), tpr.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/extensions/thirdpartyresourcedata/registry.go b/pkg/registry/extensions/thirdpartyresourcedata/registry.go index c844d0a5d2..d8266046ad 100644 --- a/pkg/registry/extensions/thirdpartyresourcedata/registry.go +++ b/pkg/registry/extensions/thirdpartyresourcedata/registry.go @@ -68,7 +68,7 @@ func (s *storage) GetThirdPartyResourceData(ctx genericapirequest.Context, name } func (s *storage) CreateThirdPartyResourceData(ctx genericapirequest.Context, ThirdPartyResourceData *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error) { - obj, err := s.Create(ctx, ThirdPartyResourceData) + obj, err := s.Create(ctx, ThirdPartyResourceData, false) return obj.(*extensions.ThirdPartyResourceData), err } diff --git a/pkg/registry/extensions/thirdpartyresourcedata/storage/storage.go b/pkg/registry/extensions/thirdpartyresourcedata/storage/storage.go index 4680e735de..3693dff82b 100644 --- a/pkg/registry/extensions/thirdpartyresourcedata/storage/storage.go +++ b/pkg/registry/extensions/thirdpartyresourcedata/storage/storage.go @@ -55,11 +55,11 @@ func (r *REST) isFrozen() bool { } // Create is a wrapper to support Freeze. -func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { if r.isFrozen() { return nil, errFrozen } - return r.Store.Create(ctx, obj) + return r.Store.Create(ctx, obj, includeUninitialized) } // Update is a wrapper to support Freeze. diff --git a/pkg/registry/extensions/thirdpartyresourcedata/strategy.go b/pkg/registry/extensions/thirdpartyresourcedata/strategy.go index 287c07d615..ba3746d45c 100644 --- a/pkg/registry/extensions/thirdpartyresourcedata/strategy.go +++ b/pkg/registry/extensions/thirdpartyresourcedata/strategy.go @@ -77,12 +77,12 @@ func (strategy) AllowUnconditionalUpdate() bool { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { tprd, ok := obj.(*extensions.ThirdPartyResourceData) if !ok { - return nil, nil, fmt.Errorf("not a ThirdPartyResourceData") + return nil, nil, false, fmt.Errorf("not a ThirdPartyResourceData") } - return labels.Set(tprd.Labels), SelectableFields(tprd), nil + return labels.Set(tprd.Labels), SelectableFields(tprd), tprd.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/networking/networkpolicy/registry.go b/pkg/registry/networking/networkpolicy/registry.go index 36f1d3d5db..1efcf8ed24 100644 --- a/pkg/registry/networking/networkpolicy/registry.go +++ b/pkg/registry/networking/networkpolicy/registry.go @@ -57,7 +57,7 @@ func (s *storage) ListNetworkPolicies(ctx genericapirequest.Context, options *me } func (s *storage) CreateNetworkPolicy(ctx genericapirequest.Context, np *networking.NetworkPolicy) error { - _, err := s.Create(ctx, np) + _, err := s.Create(ctx, np, false) return err } diff --git a/pkg/registry/networking/networkpolicy/strategy.go b/pkg/registry/networking/networkpolicy/strategy.go index b6b027fb5e..2b58c6c128 100644 --- a/pkg/registry/networking/networkpolicy/strategy.go +++ b/pkg/registry/networking/networkpolicy/strategy.go @@ -98,12 +98,12 @@ func SelectableFields(networkPolicy *networking.NetworkPolicy) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { networkPolicy, ok := obj.(*networking.NetworkPolicy) if !ok { - return nil, nil, fmt.Errorf("given object is not a NetworkPolicy.") + return nil, nil, false, fmt.Errorf("given object is not a NetworkPolicy.") } - return labels.Set(networkPolicy.ObjectMeta.Labels), SelectableFields(networkPolicy), nil + return labels.Set(networkPolicy.ObjectMeta.Labels), SelectableFields(networkPolicy), networkPolicy.Initializers != nil, nil } // Matcher is the filter used by the generic etcd backend to watch events diff --git a/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go b/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go index ab8e8851e4..4972c1d83b 100644 --- a/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go +++ b/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go @@ -42,7 +42,7 @@ func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) // createPodDisruptionBudget is a helper function that returns a PodDisruptionBudget with the updated resource version. func createPodDisruptionBudget(storage *REST, pdb policy.PodDisruptionBudget, t *testing.T) (policy.PodDisruptionBudget, error) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), pdb.Namespace) - obj, err := storage.Create(ctx, &pdb) + obj, err := storage.Create(ctx, &pdb, false) if err != nil { t.Errorf("Failed to create PodDisruptionBudget, %v", err) } diff --git a/pkg/registry/policy/poddisruptionbudget/strategy.go b/pkg/registry/policy/poddisruptionbudget/strategy.go index 5a68bf6623..a0f6a71d22 100644 --- a/pkg/registry/policy/poddisruptionbudget/strategy.go +++ b/pkg/registry/policy/poddisruptionbudget/strategy.go @@ -105,12 +105,12 @@ func PodDisruptionBudgetToSelectableFields(podDisruptionBudget *policy.PodDisrup } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { podDisruptionBudget, ok := obj.(*policy.PodDisruptionBudget) if !ok { - return nil, nil, fmt.Errorf("given object is not a PodDisruptionBudget.") + return nil, nil, false, fmt.Errorf("given object is not a PodDisruptionBudget.") } - return labels.Set(podDisruptionBudget.ObjectMeta.Labels), PodDisruptionBudgetToSelectableFields(podDisruptionBudget), nil + return labels.Set(podDisruptionBudget.ObjectMeta.Labels), PodDisruptionBudgetToSelectableFields(podDisruptionBudget), podDisruptionBudget.Initializers != nil, nil } // MatchPodDisruptionBudget is the filter used by the generic etcd backend to watch events diff --git a/pkg/registry/rbac/clusterrole/policybased/storage.go b/pkg/registry/rbac/clusterrole/policybased/storage.go index 12ad18b585..7350e91695 100644 --- a/pkg/registry/rbac/clusterrole/policybased/storage.go +++ b/pkg/registry/rbac/clusterrole/policybased/storage.go @@ -39,9 +39,9 @@ func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.Auth return &Storage{s, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } clusterRole := obj.(*rbac.ClusterRole) @@ -49,7 +49,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) } - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { diff --git a/pkg/registry/rbac/clusterrole/registry.go b/pkg/registry/rbac/clusterrole/registry.go index df6b586387..6af72e1fa1 100644 --- a/pkg/registry/rbac/clusterrole/registry.go +++ b/pkg/registry/rbac/clusterrole/registry.go @@ -57,7 +57,7 @@ func (s *storage) ListClusterRoles(ctx genericapirequest.Context, options *metai } func (s *storage) CreateClusterRole(ctx genericapirequest.Context, clusterRole *rbac.ClusterRole) error { - _, err := s.Create(ctx, clusterRole) + _, err := s.Create(ctx, clusterRole, false) return err } diff --git a/pkg/registry/rbac/clusterrole/strategy.go b/pkg/registry/rbac/clusterrole/strategy.go index 2442ffa491..ead2a876f2 100644 --- a/pkg/registry/rbac/clusterrole/strategy.go +++ b/pkg/registry/rbac/clusterrole/strategy.go @@ -104,12 +104,12 @@ func (s strategy) Export(ctx genericapirequest.Context, obj runtime.Object, exac } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { role, ok := obj.(*rbac.ClusterRole) if !ok { - return nil, nil, fmt.Errorf("not a ClusterRole") + return nil, nil, false, fmt.Errorf("not a ClusterRole") } - return labels.Set(role.Labels), SelectableFields(role), nil + return labels.Set(role.Labels), SelectableFields(role), role.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go index 6794f88c69..00f197a6f0 100644 --- a/pkg/registry/rbac/clusterrolebinding/policybased/storage.go +++ b/pkg/registry/rbac/clusterrolebinding/policybased/storage.go @@ -43,14 +43,14 @@ func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleRe return &Storage{s, authorizer, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } clusterRoleBinding := obj.(*rbac.ClusterRoleBinding) if rbacregistry.BindingAuthorized(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone, s.authorizer) { - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } rules, err := s.ruleResolver.GetRoleReferenceRules(clusterRoleBinding.RoleRef, metav1.NamespaceNone) @@ -60,7 +60,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, clusterRoleBinding.Name, err) } - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { diff --git a/pkg/registry/rbac/clusterrolebinding/registry.go b/pkg/registry/rbac/clusterrolebinding/registry.go index 1764f4a738..525c464e83 100644 --- a/pkg/registry/rbac/clusterrolebinding/registry.go +++ b/pkg/registry/rbac/clusterrolebinding/registry.go @@ -57,7 +57,7 @@ func (s *storage) ListClusterRoleBindings(ctx genericapirequest.Context, options } func (s *storage) CreateClusterRoleBinding(ctx genericapirequest.Context, clusterRoleBinding *rbac.ClusterRoleBinding) error { - _, err := s.Create(ctx, clusterRoleBinding) + _, err := s.Create(ctx, clusterRoleBinding, false) return err } diff --git a/pkg/registry/rbac/clusterrolebinding/strategy.go b/pkg/registry/rbac/clusterrolebinding/strategy.go index 0b3801f265..bb9168d51d 100644 --- a/pkg/registry/rbac/clusterrolebinding/strategy.go +++ b/pkg/registry/rbac/clusterrolebinding/strategy.go @@ -104,12 +104,12 @@ func (s strategy) Export(ctx genericapirequest.Context, obj runtime.Object, exac } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { roleBinding, ok := obj.(*rbac.ClusterRoleBinding) if !ok { - return nil, nil, fmt.Errorf("not a ClusterRoleBinding") + return nil, nil, false, fmt.Errorf("not a ClusterRoleBinding") } - return labels.Set(roleBinding.Labels), SelectableFields(roleBinding), nil + return labels.Set(roleBinding.Labels), SelectableFields(roleBinding), roleBinding.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/rbac/role/policybased/storage.go b/pkg/registry/rbac/role/policybased/storage.go index 879b10419f..c2b1a34977 100644 --- a/pkg/registry/rbac/role/policybased/storage.go +++ b/pkg/registry/rbac/role/policybased/storage.go @@ -39,9 +39,9 @@ func NewStorage(s rest.StandardStorage, ruleResolver rbacregistryvalidation.Auth return &Storage{s, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } role := obj.(*rbac.Role) @@ -49,7 +49,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, role.Name, err) } - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { diff --git a/pkg/registry/rbac/role/registry.go b/pkg/registry/rbac/role/registry.go index 9c3ea883b4..080cbd6406 100644 --- a/pkg/registry/rbac/role/registry.go +++ b/pkg/registry/rbac/role/registry.go @@ -57,7 +57,7 @@ func (s *storage) ListRoles(ctx genericapirequest.Context, options *metainternal } func (s *storage) CreateRole(ctx genericapirequest.Context, role *rbac.Role) error { - _, err := s.Create(ctx, role) + _, err := s.Create(ctx, role, false) return err } diff --git a/pkg/registry/rbac/role/strategy.go b/pkg/registry/rbac/role/strategy.go index 9a6ef33e40..aaf2a56abb 100644 --- a/pkg/registry/rbac/role/strategy.go +++ b/pkg/registry/rbac/role/strategy.go @@ -104,12 +104,12 @@ func (s strategy) Export(ctx genericapirequest.Context, obj runtime.Object, exac } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { role, ok := obj.(*rbac.Role) if !ok { - return nil, nil, fmt.Errorf("not a Role") + return nil, nil, false, fmt.Errorf("not a Role") } - return labels.Set(role.Labels), SelectableFields(role), nil + return labels.Set(role.Labels), SelectableFields(role), role.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/rbac/rolebinding/policybased/storage.go b/pkg/registry/rbac/rolebinding/policybased/storage.go index ad53ea3a6c..7f72a0be63 100644 --- a/pkg/registry/rbac/rolebinding/policybased/storage.go +++ b/pkg/registry/rbac/rolebinding/policybased/storage.go @@ -42,9 +42,9 @@ func NewStorage(s rest.StandardStorage, authorizer authorizer.Authorizer, ruleRe return &Storage{s, authorizer, ruleResolver} } -func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { if rbacregistry.EscalationAllowed(ctx) { - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } // Get the namespace from the context (populated from the URL). @@ -56,7 +56,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run roleBinding := obj.(*rbac.RoleBinding) if rbacregistry.BindingAuthorized(ctx, roleBinding.RoleRef, namespace, s.authorizer) { - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } rules, err := s.ruleResolver.GetRoleReferenceRules(roleBinding.RoleRef, namespace) @@ -66,7 +66,7 @@ func (s *Storage) Create(ctx genericapirequest.Context, obj runtime.Object) (run if err := rbacregistryvalidation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, roleBinding.Name, err) } - return s.StandardStorage.Create(ctx, obj) + return s.StandardStorage.Create(ctx, obj, includeUninitialized) } func (s *Storage) Update(ctx genericapirequest.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { diff --git a/pkg/registry/rbac/rolebinding/registry.go b/pkg/registry/rbac/rolebinding/registry.go index c468cb7166..a2e483b56a 100644 --- a/pkg/registry/rbac/rolebinding/registry.go +++ b/pkg/registry/rbac/rolebinding/registry.go @@ -58,7 +58,7 @@ func (s *storage) ListRoleBindings(ctx genericapirequest.Context, options *metai func (s *storage) CreateRoleBinding(ctx genericapirequest.Context, roleBinding *rbac.RoleBinding) error { // TODO(ericchiang): add additional validation - _, err := s.Create(ctx, roleBinding) + _, err := s.Create(ctx, roleBinding, false) return err } diff --git a/pkg/registry/rbac/rolebinding/strategy.go b/pkg/registry/rbac/rolebinding/strategy.go index 0d1d5e9820..f5291ab0b2 100644 --- a/pkg/registry/rbac/rolebinding/strategy.go +++ b/pkg/registry/rbac/rolebinding/strategy.go @@ -104,12 +104,12 @@ func (s strategy) Export(ctx genericapirequest.Context, obj runtime.Object, exac } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { roleBinding, ok := obj.(*rbac.RoleBinding) if !ok { - return nil, nil, fmt.Errorf("not a RoleBinding") + return nil, nil, false, fmt.Errorf("not a RoleBinding") } - return labels.Set(roleBinding.Labels), SelectableFields(roleBinding), nil + return labels.Set(roleBinding.Labels), SelectableFields(roleBinding), roleBinding.Initializers != nil, nil } // Matcher returns a generic matcher for a given label and field selector. diff --git a/pkg/registry/settings/podpreset/registry.go b/pkg/registry/settings/podpreset/registry.go index a6225de3b9..d1c96f9a38 100644 --- a/pkg/registry/settings/podpreset/registry.go +++ b/pkg/registry/settings/podpreset/registry.go @@ -57,7 +57,7 @@ func (s *storage) ListPodPresets(ctx genericapirequest.Context, options *metaint } func (s *storage) CreatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error { - _, err := s.Create(ctx, pp) + _, err := s.Create(ctx, pp, false) return err } diff --git a/pkg/registry/settings/podpreset/strategy.go b/pkg/registry/settings/podpreset/strategy.go index 81acd7298d..6eb0f94acd 100644 --- a/pkg/registry/settings/podpreset/strategy.go +++ b/pkg/registry/settings/podpreset/strategy.go @@ -93,12 +93,12 @@ func SelectableFields(pip *settings.PodPreset) fields.Set { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pip, ok := obj.(*settings.PodPreset) if !ok { - return nil, nil, fmt.Errorf("given object is not a PodPreset.") + return nil, nil, false, fmt.Errorf("given object is not a PodPreset.") } - return labels.Set(pip.ObjectMeta.Labels), SelectableFields(pip), nil + return labels.Set(pip.ObjectMeta.Labels), SelectableFields(pip), pip.Initializers != nil, nil } // Matcher is the filter used by the generic etcd backend to watch events diff --git a/pkg/registry/storage/storageclass/strategy.go b/pkg/registry/storage/storageclass/strategy.go index 4c48c3dbc5..73aa7d2b37 100644 --- a/pkg/registry/storage/storageclass/strategy.go +++ b/pkg/registry/storage/storageclass/strategy.go @@ -80,12 +80,12 @@ func (storageClassStrategy) AllowUnconditionalUpdate() bool { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { cls, ok := obj.(*storage.StorageClass) if !ok { - return nil, nil, fmt.Errorf("given object is not of type StorageClass") + return nil, nil, false, fmt.Errorf("given object is not of type StorageClass") } - return labels.Set(cls.ObjectMeta.Labels), StorageClassToSelectableFields(cls), nil + return labels.Set(cls.ObjectMeta.Labels), StorageClassToSelectableFields(cls), cls.Initializers != nil, nil } // MatchStorageClass returns a generic matcher for a given label and field selector. diff --git a/plugin/BUILD b/plugin/BUILD index 56e40f49a7..0aaec7ece7 100644 --- a/plugin/BUILD +++ b/plugin/BUILD @@ -22,6 +22,7 @@ filegroup( "//plugin/pkg/admission/exec:all-srcs", "//plugin/pkg/admission/gc:all-srcs", "//plugin/pkg/admission/imagepolicy:all-srcs", + "//plugin/pkg/admission/initialization:all-srcs", "//plugin/pkg/admission/initialresources:all-srcs", "//plugin/pkg/admission/limitranger:all-srcs", "//plugin/pkg/admission/namespace/autoprovision:all-srcs", diff --git a/plugin/pkg/admission/initialization/BUILD b/plugin/pkg/admission/initialization/BUILD new file mode 100644 index 0000000000..88838997ef --- /dev/null +++ b/plugin/pkg/admission/initialization/BUILD @@ -0,0 +1,38 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["initialization.go"], + tags = ["automanaged"], + deps = [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/admission/initialization/initialization.go b/plugin/pkg/admission/initialization/initialization.go new file mode 100644 index 0000000000..15855053c4 --- /dev/null +++ b/plugin/pkg/admission/initialization/initialization.go @@ -0,0 +1,173 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package initialization + +import ( + "fmt" + "io" + + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/validation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("Initializers", func(config io.Reader) (admission.Interface, error) { + return NewInitializer(), nil + }) +} + +type initializerOptions struct { + Initializers []string +} + +type initializer struct { + resources map[schema.GroupResource]initializerOptions + authorizer authorizer.Authorizer +} + +// NewAlwaysAdmit creates a new always admit admission handler +func NewInitializer() admission.Interface { + return &initializer{ + resources: map[schema.GroupResource]initializerOptions{ + //schema.GroupResource{Resource: "pods"}: {Initializers: []string{"Test"}}, + }, + } +} + +func (i *initializer) Validate() error { + return nil +} + +func (i *initializer) SetAuthorizer(a authorizer.Authorizer) { + i.authorizer = a +} + +var initializerFieldPath = field.NewPath("metadata", "initializers") + +func (i *initializer) Admit(a admission.Attributes) (err error) { + // TODO: sub-resource action should be denied until the object is initialized + if len(a.GetSubresource()) > 0 { + return nil + } + + resource, ok := i.resources[a.GetResource().GroupResource()] + if !ok { + return nil + } + + switch a.GetOperation() { + case admission.Create: + accessor, err := meta.Accessor(a.GetObject()) + if err != nil { + // objects without meta accessor cannot be checked for initialization, and it is possible to make calls + // via our API that don't have ObjectMeta + return nil + } + existing := accessor.GetInitializers() + // it must be possible for some users to bypass initialization - for now, check the initialize operation + if existing != nil { + if err := i.canInitialize(a); err != nil { + return err + } + } + + // TODO: pull this from config + accessor.SetInitializers(copiedInitializers(resource.Initializers)) + + case admission.Update: + accessor, err := meta.Accessor(a.GetObject()) + if err != nil { + // objects without meta accessor cannot be checked for initialization, and it is possible to make calls + // via our API that don't have ObjectMeta + return nil + } + updated := accessor.GetInitializers() + + existingAccessor, err := meta.Accessor(a.GetOldObject()) + if err != nil { + // if the old object does not have an accessor, but the new one does, error out + return fmt.Errorf("initialized resources must be able to set initializers (%T): %v", a.GetOldObject(), err) + } + existing := existingAccessor.GetInitializers() + + // because we are called before validation, we need to ensure the update transition is valid. + if errs := validation.ValidateInitializersUpdate(updated, existing, initializerFieldPath); len(errs) > 0 { + return errors.NewInvalid(a.GetKind().GroupKind(), a.GetName(), errs) + } + + // caller must have the ability to mutate un-initialized resources + if err := i.canInitialize(a); err != nil { + return err + } + + // TODO: restrict initialization list changes to specific clients? + } + + return nil +} + +func (i *initializer) canInitialize(a admission.Attributes) error { + // if no authorizer is present, the initializer plugin allows modification of uninitialized resources + if i.authorizer == nil { + glog.V(4).Infof("No authorizer provided to initialization admission control, unable to check permissions") + return nil + } + // caller must have the ability to mutate un-initialized resources + authorized, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{ + Name: a.GetName(), + ResourceRequest: true, + User: a.GetUserInfo(), + Verb: "initialize", + Namespace: a.GetNamespace(), + APIGroup: a.GetResource().Group, + APIVersion: a.GetResource().Version, + Resource: a.GetResource().Resource, + }) + if err != nil { + return err + } + if !authorized { + return fmt.Errorf("user must have permission to initialize resources: %s", reason) + } + return nil +} + +func (i *initializer) Handles(op admission.Operation) bool { + return true +} + +func copiedInitializers(names []string) *metav1.Initializers { + if len(names) == 0 { + return nil + } + var init []metav1.Initializer + for _, name := range names { + init = append(init, metav1.Initializer{Name: name}) + } + return &metav1.Initializers{ + Pending: init, + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go index 04f605843d..84bd9cdedc 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go @@ -184,10 +184,41 @@ func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, name allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...) allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) + allErrs = append(allErrs, ValidateInitializers(meta.GetInitializers(), fldPath.Child("initializers"))...) allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...) return allErrs } +func ValidateInitializers(initializers *metav1.Initializers, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if initializers == nil { + return allErrs + } + for i, initializer := range initializers.Pending { + for _, msg := range validation.IsQualifiedName(initializer.Name) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("pending").Index(i), initializer.Name, msg)) + } + } + allErrs = append(allErrs, validateInitializersResult(initializers.Result, fldPath.Child("result"))...) + if len(initializers.Pending) == 0 && initializers.Result == nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("pending"), nil, "must be non-empty when result is not set")) + } + return allErrs +} + +func validateInitializersResult(result *metav1.Status, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if result == nil { + return allErrs + } + switch result.Status { + case metav1.StatusFailure: + default: + allErrs = append(allErrs, field.Invalid(fldPath.Child("status"), result.Status, "must be 'Failure'")) + } + return allErrs +} + // ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers. func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -226,7 +257,7 @@ func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *fiel } func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} + var allErrs field.ErrorList if !RepairMalformedUpdates && newMeta.GetUID() != oldMeta.GetUID() { allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), newMeta.GetUID(), "field is immutable")) @@ -276,6 +307,8 @@ func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *f allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented")) } + allErrs = append(allErrs, ValidateInitializersUpdate(newMeta.GetInitializers(), oldMeta.GetInitializers(), fldPath.Child("initializers"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...) @@ -288,3 +321,28 @@ func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *f return allErrs } + +// ValidateInitializersUpdate checks the update of the metadata initializers field +func ValidateInitializersUpdate(newInit, oldInit *metav1.Initializers, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + switch { + case oldInit == nil && newInit != nil: + // Initializers may not be set on new objects + allErrs = append(allErrs, field.Invalid(fldPath, nil, "field is immutable once initialization has completed")) + case oldInit != nil && newInit == nil: + // this is a valid transition and means initialization was successful + case oldInit != nil && newInit != nil: + // validate changes to initializers + switch { + case oldInit.Result == nil && newInit.Result != nil: + // setting a result is allowed + allErrs = append(allErrs, validateInitializersResult(newInit.Result, fldPath.Child("result"))...) + case oldInit.Result != nil: + // setting Result implies permanent failure, and all future updates will be prevented + allErrs = append(allErrs, ValidateImmutableField(newInit.Result, oldInit.Result, fldPath.Child("result"))...) + default: + // leaving the result nil is allowed + } + } + return allErrs +} diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go index 73e7d47562..eff9a7e12e 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go @@ -18,6 +18,7 @@ package internalversion import ( "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/util/validation/field" @@ -30,6 +31,7 @@ func Convert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out if err := metav1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { return err } + out.IncludeUninitialized = in.IncludeUninitialized out.ResourceVersion = in.ResourceVersion out.TimeoutSeconds = in.TimeoutSeconds out.Watch = in.Watch @@ -43,6 +45,7 @@ func Convert_v1_ListOptions_To_internalversion_ListOptions(in *metav1.ListOption if err := metav1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { return err } + out.IncludeUninitialized = in.IncludeUninitialized out.ResourceVersion = in.ResourceVersion out.TimeoutSeconds = in.TimeoutSeconds out.Watch = in.Watch diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go index 576baeac64..0aa4188df2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go @@ -33,7 +33,7 @@ type ListOptions struct { FieldSelector fields.Selector // If true, partially initialized resources are included in the response. // +optional - IncludeUninitialized bool `json:"includeUninitialized,omitempty"` + IncludeUninitialized bool // If true, watch for changes to this list Watch bool // When specified with a watch call, shows changes that occur after that particular version of a resource. diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD index 0b4366c93a..b6b0d2bb5d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/BUILD @@ -22,9 +22,11 @@ go_library( deps = [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", ], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go index a74ed02689..d9a4f887b7 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go @@ -27,10 +27,12 @@ import ( "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // Unstructured allows objects that do not have Golang structs registered to be manipulated @@ -452,12 +454,34 @@ func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { return gvk } +var converter = unstructured.NewConverter(false) + func (u *Unstructured) GetInitializers() *metav1.Initializers { - panic("not implemented") + field := getNestedField(u.Object, "metadata", "initializers") + if field == nil { + return nil + } + obj, ok := field.(map[string]interface{}) + if !ok { + return nil + } + out := &metav1.Initializers{} + if err := converter.FromUnstructured(obj, out); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err)) + } + return out } func (u *Unstructured) SetInitializers(initializers *metav1.Initializers) { - panic("not implemented") + if initializers == nil { + setNestedField(u.Object, nil, "metadata", "initializers") + return + } + out := make(map[string]interface{}) + if err := converter.ToUnstructured(initializers, &out); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to retrieve initializers for object: %v", err)) + } + setNestedField(u.Object, out, "metadata", "initializers") } func (u *Unstructured) GetFinalizers() []string { diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD index b0337b40f4..0804afc330 100644 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/BUILD @@ -31,7 +31,6 @@ go_library( deps = [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/converter.go b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/converter.go index fc28406b0b..cf84a61989 100644 --- a/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/converter.go +++ b/staging/src/k8s.io/apimachinery/pkg/conversion/unstructured/converter.go @@ -29,7 +29,6 @@ import ( "sync/atomic" apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/json" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -37,11 +36,11 @@ import ( "github.com/golang/glog" ) -// Converter is an interface for converting between runtime.Object +// Converter is an interface for converting between interface{} // and map[string]interface representation. type Converter interface { - ToUnstructured(obj runtime.Object, u *map[string]interface{}) error - FromUnstructured(u map[string]interface{}, obj runtime.Object) error + ToUnstructured(obj interface{}, u *map[string]interface{}) error + FromUnstructured(u map[string]interface{}, obj interface{}) error } type structField struct { @@ -92,7 +91,7 @@ func parseBool(key string) bool { return value } -// ConverterImpl knows how to convert betweek runtime.Object and +// ConverterImpl knows how to convert between interface{} and // Unstructured in both ways. type converterImpl struct { // If true, we will be additionally running conversion via json @@ -107,10 +106,15 @@ func NewConverter(mismatchDetection bool) Converter { } } -func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj runtime.Object) error { - err := fromUnstructured(reflect.ValueOf(u), reflect.ValueOf(obj).Elem()) +func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj interface{}) error { + t := reflect.TypeOf(obj) + value := reflect.ValueOf(obj) + if t.Kind() != reflect.Ptr || value.IsNil() { + return fmt.Errorf("FromUnstructured requires a non-nil pointer to an object, got %v", t) + } + err := fromUnstructured(reflect.ValueOf(u), value.Elem()) if c.mismatchDetection { - newObj := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) + newObj := reflect.New(t.Elem()).Interface() newErr := fromUnstructuredViaJSON(u, newObj) if (err != nil) != (newErr != nil) { glog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err) @@ -122,7 +126,7 @@ func (c *converterImpl) FromUnstructured(u map[string]interface{}, obj runtime.O return err } -func fromUnstructuredViaJSON(u map[string]interface{}, obj runtime.Object) error { +func fromUnstructuredViaJSON(u map[string]interface{}, obj interface{}) error { data, err := json.Marshal(u) if err != nil { return err @@ -384,8 +388,13 @@ func interfaceFromUnstructured(sv, dv reflect.Value) error { return nil } -func (c *converterImpl) ToUnstructured(obj runtime.Object, u *map[string]interface{}) error { - err := toUnstructured(reflect.ValueOf(obj).Elem(), reflect.ValueOf(u).Elem()) +func (c *converterImpl) ToUnstructured(obj interface{}, u *map[string]interface{}) error { + t := reflect.TypeOf(obj) + value := reflect.ValueOf(obj) + if t.Kind() != reflect.Ptr || value.IsNil() { + return fmt.Errorf("ToUnstructured requires a non-nil pointer to an object, got %v", t) + } + err := toUnstructured(value.Elem(), reflect.ValueOf(u).Elem()) if c.mismatchDetection { newUnstr := &map[string]interface{}{} newErr := toUnstructuredViaJSON(obj, newUnstr) @@ -399,7 +408,7 @@ func (c *converterImpl) ToUnstructured(obj runtime.Object, u *map[string]interfa return err } -func toUnstructuredViaJSON(obj runtime.Object, u *map[string]interface{}) error { +func toUnstructuredViaJSON(obj interface{}, u *map[string]interface{}) error { data, err := json.Marshal(obj) if err != nil { return err diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go index 24f0698f9e..467e7dc577 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go @@ -407,6 +407,7 @@ type SimpleRESTStorage struct { fakeWatch *watch.FakeWatcher requestedLabelSelector labels.Selector requestedFieldSelector fields.Selector + requestedUninitialized bool requestedResourceVersion string requestedResourceNamespace string @@ -449,6 +450,7 @@ func (storage *SimpleRESTStorage) List(ctx request.Context, options *metainterna if options != nil && options.FieldSelector != nil { storage.requestedFieldSelector = options.FieldSelector } + storage.requestedUninitialized = options.IncludeUninitialized return result, storage.errors["list"] } @@ -522,7 +524,7 @@ func (storage *SimpleRESTStorage) NewList() runtime.Object { return &genericapitesting.SimpleList{} } -func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object) (runtime.Object, error) { +func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { storage.checkContext(ctx) storage.created = obj.(*genericapitesting.Simple) if err := storage.errors["create"]; err != nil { @@ -717,7 +719,7 @@ type NamedCreaterRESTStorage struct { createdName string } -func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string, obj runtime.Object) (runtime.Object, error) { +func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { storage.checkContext(ctx) storage.created = obj.(*genericapitesting.Simple) storage.createdName = name @@ -1470,6 +1472,52 @@ func TestGet(t *testing.T) { } } +func TestGetUninitialized(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + list: []genericapitesting.Simple{ + { + ObjectMeta: metav1.ObjectMeta{ + Initializers: &metav1.Initializers{ + Pending: []metav1.Initializer{{Name: "test"}}, + }, + }, + Other: "foo", + }, + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", + alternativeSet: sets.NewString("/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple"), + name: "id", + namespace: "default", + } + storage["simple"] = &simpleStorage + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple?includeUninitialized=true") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + var itemOut genericapitesting.SimpleList + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(itemOut.Items) != 1 || itemOut.Items[0].Other != "foo" { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) + } + if !simpleStorage.requestedUninitialized { + t.Errorf("Didn't set correct flag") + } +} + func TestGetPretty(t *testing.T) { storage := map[string]rest.Storage{} simpleStorage := SimpleRESTStorage{ diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go index 578d1c95ff..e57b694b61 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go @@ -449,13 +449,12 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object } } + // TODO: replace with content type negotiation? + includeUninitialized := req.URL.Query().Get("includeUninitialized") == "1" + trace.Step("About to store object in database") result, err := finishRequest(timeout, func() (runtime.Object, error) { - out, err := r.Create(ctx, name, obj) - if status, ok := out.(*metav1.Status); ok && err == nil && status.Code == 0 { - status.Code = http.StatusCreated - } - return out, err + return r.Create(ctx, name, obj, includeUninitialized) }) if err != nil { scope.err(err, w, req) @@ -474,7 +473,19 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object } trace.Step("Self-link added") - transformResponseObject(ctx, scope, req, w, http.StatusCreated, result) + // If the object is partially initialized, always indicate it via StatusAccepted + code := http.StatusCreated + if accessor, err := meta.Accessor(result); err == nil { + if accessor.GetInitializers() != nil { + code = http.StatusAccepted + } + } + status, ok := result.(*metav1.Status) + if ok && err == nil && status.Code == 0 { + status.Code = int32(code) + } + + transformResponseObject(ctx, scope, req, w, code, result) } } @@ -492,8 +503,8 @@ type namedCreaterAdapter struct { rest.Creater } -func (c *namedCreaterAdapter) Create(ctx request.Context, name string, obj runtime.Object) (runtime.Object, error) { - return c.Creater.Create(ctx, obj) +func (c *namedCreaterAdapter) Create(ctx request.Context, name string, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { + return c.Creater.Create(ctx, obj, includeUninitialized) } // PatchResource returns a function that will handle a resource patch diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go index 74ddaee2c6..77b55229e8 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go @@ -259,6 +259,7 @@ func (e *Store) ListPredicate(ctx genericapirequest.Context, p storage.Selection // By default we should serve the request from etcd. options = &metainternalversion.ListOptions{ResourceVersion: ""} } + p.IncludeUninitialized = options.IncludeUninitialized list := e.NewListFunc() if name, ok := p.MatchesSingle(); ok { if key, err := e.KeyFunc(ctx, name); err == nil { @@ -273,7 +274,7 @@ func (e *Store) ListPredicate(ctx genericapirequest.Context, p storage.Selection } // Create inserts a new item according to the unique key from the object. -func (e *Store) Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { +func (e *Store) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) { if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { return nil, err } @@ -319,15 +320,91 @@ func (e *Store) Create(ctx genericapirequest.Context, obj runtime.Object) (runti return nil, err } } + if !includeUninitialized { + return e.WaitForInitialized(ctx, out) + } return out, nil } +func (e *Store) WaitForInitialized(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { + // return early if we don't have initializers, or if they've completed already + accessor, err := meta.Accessor(obj) + if err != nil { + return obj, nil + } + initializers := accessor.GetInitializers() + if initializers == nil { + return obj, nil + } + if result := initializers.Result; result != nil { + return nil, kubeerr.FromObject(result) + } + + key, err := e.KeyFunc(ctx, accessor.GetName()) + if err != nil { + return nil, err + } + w, err := e.Storage.Watch(ctx, key, accessor.GetResourceVersion(), storage.SelectionPredicate{ + Label: labels.Everything(), + Field: fields.Everything(), + + IncludeUninitialized: true, + }) + if err != nil { + return nil, err + } + defer w.Stop() + + latest := obj + ch := w.ResultChan() + for { + select { + case event, ok := <-ch: + if !ok { + // TODO: should we just expose the partially initialized object? + return nil, kubeerr.NewServerTimeout(e.QualifiedResource, "create", 0) + } + switch event.Type { + case watch.Deleted: + if latest = event.Object; latest != nil { + if accessor, err := meta.Accessor(latest); err == nil { + if initializers := accessor.GetInitializers(); initializers != nil && initializers.Result != nil { + // initialization failed, but we missed the modification event + return nil, kubeerr.FromObject(initializers.Result) + } + } + } + return nil, kubeerr.NewInternalError(fmt.Errorf("object deleted while waiting for creation")) + case watch.Error: + if status, ok := event.Object.(*metav1.Status); ok { + return nil, &kubeerr.StatusError{ErrStatus: *status} + } + return nil, kubeerr.NewInternalError(fmt.Errorf("unexpected object in watch stream, can't complete initialization %T", event.Object)) + case watch.Modified: + latest = event.Object + accessor, err = meta.Accessor(latest) + if err != nil { + return nil, kubeerr.NewInternalError(fmt.Errorf("object no longer has access to metadata %T: %v", latest, err)) + } + initializers := accessor.GetInitializers() + if initializers == nil { + // completed initialization + return latest, nil + } + if result := initializers.Result; result != nil { + // initialization failed + return nil, kubeerr.FromObject(result) + } + } + case <-ctx.Done(): + } + } +} + // shouldDeleteDuringUpdate checks if a Update is removing all the object's // finalizers. If so, it further checks if the object's -// DeletionGracePeriodSeconds is 0. If so, it returns true. -// -// If the store does not have garbage collection enabled, -// shouldDeleteDuringUpdate will always return false. +// DeletionGracePeriodSeconds is 0. If so, it returns true. If garbage collection +// is disabled it always returns false. func (e *Store) shouldDeleteDuringUpdate(ctx genericapirequest.Context, key string, obj, existing runtime.Object) bool { if !e.EnableGarbageCollection { return false @@ -345,9 +422,23 @@ func (e *Store) shouldDeleteDuringUpdate(ctx genericapirequest.Context, key stri return len(newMeta.GetFinalizers()) == 0 && oldMeta.GetDeletionGracePeriodSeconds() != nil && *oldMeta.GetDeletionGracePeriodSeconds() == 0 } -// deleteForEmptyFinalizers handles deleting an object once its finalizer list -// becomes empty due to an update. -func (e *Store) deleteForEmptyFinalizers(ctx genericapirequest.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions) (runtime.Object, bool, error) { +// shouldDeleteForFailedInitialization returns true if the provided object is initializing and has +// a failure recorded. +func (e *Store) shouldDeleteForFailedInitialization(ctx genericapirequest.Context, obj runtime.Object) bool { + m, err := meta.Accessor(obj) + if err != nil { + utilruntime.HandleError(err) + return false + } + if initializers := m.GetInitializers(); initializers != nil && initializers.Result != nil { + return true + } + return false +} + +// deleteWithoutFinalizers handles deleting an object ignoring its finalizer list. +// Used for objects that are either been finalized or have never initialized. +func (e *Store) deleteWithoutFinalizers(ctx genericapirequest.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions) (runtime.Object, bool, error) { out := e.NewFunc() glog.V(6).Infof("going to delete %s from registry, triggered by update", name) if err := e.Storage.Delete(ctx, key, out, preconditions); err != nil { @@ -477,7 +568,7 @@ func (e *Store) Update(ctx genericapirequest.Context, name string, objInfo rest. if err != nil { // delete the object if err == errEmptiedFinalizers { - return e.deleteForEmptyFinalizers(ctx, name, key, deleteObj, storagePreconditions) + return e.deleteWithoutFinalizers(ctx, name, key, deleteObj, storagePreconditions) } if creating { err = storeerr.InterpretCreateError(err, e.QualifiedResource, name) @@ -487,6 +578,11 @@ func (e *Store) Update(ctx genericapirequest.Context, name string, objInfo rest. } return nil, false, err } + + if e.shouldDeleteForFailedInitialization(ctx, out) { + return e.deleteWithoutFinalizers(ctx, name, key, out, storagePreconditions) + } + if creating { if e.AfterCreate != nil { if err := e.AfterCreate(out); err != nil { @@ -1025,11 +1121,14 @@ func (e *Store) Watch(ctx genericapirequest.Context, options *metainternalversio if options != nil && options.FieldSelector != nil { field = options.FieldSelector } + predicate := e.PredicateFunc(label, field) + resourceVersion := "" if options != nil { resourceVersion = options.ResourceVersion + predicate.IncludeUninitialized = options.IncludeUninitialized } - return e.WatchPredicate(ctx, e.PredicateFunc(label, field), resourceVersion) + return e.WatchPredicate(ctx, predicate, resourceVersion) } // WatchPredicate starts a watch for the items that m matches. diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go index d589d57256..d0443115ef 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go @@ -39,6 +39,7 @@ import ( "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/apis/example" examplev1 "k8s.io/apiserver/pkg/apis/example/v1" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" @@ -117,9 +118,9 @@ func NewTestGenericStoreRegistry(t *testing.T) (factory.DestroyFunc, *Store) { return newTestGenericStoreRegistry(t, scheme, false) } -func getPodAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func getPodAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod := obj.(*example.Pod) - return labels.Set{"name": pod.ObjectMeta.Name}, nil, nil + return labels.Set{"name": pod.ObjectMeta.Name}, nil, pod.Initializers != nil, nil } // matchPodName returns selection predicate that matches any pod with name in the set. @@ -142,8 +143,8 @@ func matchEverything() storage.SelectionPredicate { return storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.Everything(), - GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, err error) { - return nil, nil, nil + GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) { + return nil, nil, false, nil }, } } @@ -238,7 +239,7 @@ func TestStoreListResourceVersion(t *testing.T) { destroyFunc, registry := newTestGenericStoreRegistry(t, scheme, true) defer destroyFunc() - obj, err := registry.Create(ctx, fooPod) + obj, err := registry.Create(ctx, fooPod, false) if err != nil { t.Fatal(err) } @@ -268,7 +269,7 @@ func TestStoreListResourceVersion(t *testing.T) { t.Fatalf("expected waiting, but get %#v", l) } - if _, err := registry.Create(ctx, barPod); err != nil { + if _, err := registry.Create(ctx, barPod, false); err != nil { t.Fatal(err) } @@ -305,7 +306,7 @@ func TestStoreCreate(t *testing.T) { registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy} // create the object - objA, err := registry.Create(testContext, podA) + objA, err := registry.Create(testContext, podA, false) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -322,7 +323,7 @@ func TestStoreCreate(t *testing.T) { } // now try to create the second pod - _, err = registry.Create(testContext, podB) + _, err = registry.Create(testContext, podB, false) if !errors.IsAlreadyExists(err) { t.Errorf("Unexpected error: %v", err) } @@ -341,7 +342,7 @@ func TestStoreCreate(t *testing.T) { } // try to create before graceful deletion period is over - _, err = registry.Create(testContext, podA) + _, err = registry.Create(testContext, podA, false) if err == nil || !errors.IsAlreadyExists(err) { t.Fatalf("Expected 'already exists' error from storage, but got %v", err) } @@ -353,6 +354,208 @@ func TestStoreCreate(t *testing.T) { } } +func isPendingInitialization(obj metav1.Object) bool { + return obj.GetInitializers() != nil && obj.GetInitializers().Result == nil && len(obj.GetInitializers().Pending) > 0 +} + +func hasInitializers(obj metav1.Object, expected ...string) bool { + if !isPendingInitialization(obj) { + return false + } + if len(expected) != len(obj.GetInitializers().Pending) { + return false + } + for i, init := range obj.GetInitializers().Pending { + if init.Name != expected[i] { + return false + } + } + return true +} + +func isFailedInitialization(obj metav1.Object) bool { + return obj.GetInitializers() != nil && obj.GetInitializers().Result != nil && obj.GetInitializers().Result.Status == metav1.StatusFailure +} + +func isInitialized(obj metav1.Object) bool { + return obj.GetInitializers() == nil +} + +func TestStoreCreateInitialized(t *testing.T) { + podA := &example.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", Namespace: "test", + Initializers: &metav1.Initializers{ + Pending: []metav1.Initializer{{Name: "Test"}}, + }, + }, + Spec: example.PodSpec{NodeName: "machine"}, + } + + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test") + destroyFunc, registry := NewTestGenericStoreRegistry(t) + defer destroyFunc() + + ch := make(chan struct{}) + chObserver := make(chan struct{}) + + // simulate a background initializer that initializes the object + early := make(chan struct{}, 1) + go func() { + defer close(ch) + w, err := registry.Watch(ctx, &metainternalversion.ListOptions{ + IncludeUninitialized: true, + Watch: true, + FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"), + }) + if err != nil { + t.Fatal(err) + } + defer w.Stop() + event := <-w.ResultChan() + pod := event.Object.(*example.Pod) + if event.Type != watch.Added || !hasInitializers(pod, "Test") { + t.Fatalf("unexpected event: %s %#v", event.Type, event.Object) + } + + select { + case <-early: + t.Fatalf("CreateInitialized should not have returned") + default: + } + + pod.Initializers = nil + updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod, scheme)) + if err != nil { + t.Fatal(err) + } + pod = updated.(*example.Pod) + if !isInitialized(pod) { + t.Fatalf("unexpected update: %#v", pod.Initializers) + } + + event = <-w.ResultChan() + if event.Type != watch.Modified || !isInitialized(event.Object.(*example.Pod)) { + t.Fatalf("unexpected event: %s %#v", event.Type, event.Object) + } + }() + + // create a background worker that should only observe the final creation + go func() { + defer close(chObserver) + w, err := registry.Watch(ctx, &metainternalversion.ListOptions{ + IncludeUninitialized: false, + Watch: true, + FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"), + }) + if err != nil { + t.Fatal(err) + } + defer w.Stop() + + event := <-w.ResultChan() + pod := event.Object.(*example.Pod) + if event.Type != watch.Added || !isInitialized(pod) { + t.Fatalf("unexpected event: %s %#v", event.Type, event.Object) + } + }() + + // create the object + objA, err := registry.Create(ctx, podA, false) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // signal that we're now waiting, then wait for both observers to see + // the result of the create. + early <- struct{}{} + <-ch + <-chObserver + + // get the object + checkobj, err := registry.Get(ctx, podA.Name, &metav1.GetOptions{}) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // verify objects are equal + if e, a := objA, checkobj; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + } +} + +func TestStoreCreateInitializedFailed(t *testing.T) { + podA := &example.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", Namespace: "test", + Initializers: &metav1.Initializers{ + Pending: []metav1.Initializer{{Name: "Test"}}, + }, + }, + Spec: example.PodSpec{NodeName: "machine"}, + } + + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test") + destroyFunc, registry := NewTestGenericStoreRegistry(t) + defer destroyFunc() + + ch := make(chan struct{}) + go func() { + w, err := registry.Watch(ctx, &metainternalversion.ListOptions{ + IncludeUninitialized: true, + Watch: true, + FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"), + }) + if err != nil { + t.Fatal(err) + } + event := <-w.ResultChan() + pod := event.Object.(*example.Pod) + if event.Type != watch.Added || !hasInitializers(pod, "Test") { + t.Fatalf("unexpected event: %s %#v", event.Type, event.Object) + } + pod.Initializers.Pending = nil + pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure, Code: 403, Reason: metav1.StatusReasonForbidden, Message: "induced failure"} + updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod, scheme)) + if err != nil { + t.Fatal(err) + } + pod = updated.(*example.Pod) + if !isFailedInitialization(pod) { + t.Fatalf("unexpected update: %#v", pod.Initializers) + } + + event = <-w.ResultChan() + if event.Type != watch.Modified || !isFailedInitialization(event.Object.(*example.Pod)) { + t.Fatalf("unexpected event: %s %#v", event.Type, event.Object) + } + + event = <-w.ResultChan() + if event.Type != watch.Deleted || !isFailedInitialization(event.Object.(*example.Pod)) { + t.Fatalf("unexpected event: %s %#v", event.Type, event.Object) + } + w.Stop() + close(ch) + }() + + // create the object + _, err := registry.Create(ctx, podA, false) + if !errors.IsForbidden(err) { + t.Fatalf("unexpected error: %#v", err.(errors.APIStatus).Status()) + } + if err.(errors.APIStatus).Status().Message != "induced failure" { + t.Fatalf("unexpected error: %#v", err) + } + + <-ch + + // get the object + _, err = registry.Get(ctx, podA.Name, &metav1.GetOptions{}) + if !errors.IsNotFound(err) { + t.Fatalf("Unexpected error: %v", err) + } +} + func updateAndVerify(t *testing.T, ctx genericapirequest.Context, registry *Store, pod *example.Pod) bool { obj, _, err := registry.Update(ctx, pod.Name, rest.DefaultUpdatedObjectInfo(pod, scheme)) if err != nil { @@ -440,7 +643,7 @@ func TestNoOpUpdates(t *testing.T) { var err error var createResult runtime.Object - if createResult, err = registry.Create(genericapirequest.NewDefaultContext(), newPod()); err != nil { + if createResult, err = registry.Create(genericapirequest.NewDefaultContext(), newPod(), false); err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -615,7 +818,7 @@ func TestStoreDelete(t *testing.T) { } // create pod - _, err = registry.Create(testContext, podA) + _, err = registry.Create(testContext, podA, false) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -687,7 +890,7 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) { registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy} defer destroyFunc() // create pod - _, err := registry.Create(testContext, podWithFinalizer) + _, err := registry.Create(testContext, podWithFinalizer, false) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -735,6 +938,43 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) { } } +func TestFailedInitializationStoreUpdate(t *testing.T) { + initialGeneration := int64(1) + podInitializing := &example.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "Test"}}}, Generation: initialGeneration}, + Spec: example.PodSpec{NodeName: "machine"}, + } + + testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test") + destroyFunc, registry := NewTestGenericStoreRegistry(t) + registry.EnableGarbageCollection = true + defaultDeleteStrategy := testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true} + registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy} + defer destroyFunc() + + // create pod, view initializing + obj, err := registry.Create(testContext, podInitializing, true) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + pod := obj.(*example.Pod) + + // update the pod with initialization failure, the pod should be deleted + pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure} + result, _, err := registry.Update(testContext, podInitializing.Name, rest.DefaultUpdatedObjectInfo(pod, scheme)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + _, err = registry.Get(testContext, podInitializing.Name, &metav1.GetOptions{}) + if err == nil || !errors.IsNotFound(err) { + t.Fatalf("Unexpected error: %v", err) + } + pod = result.(*example.Pod) + if pod.Initializers == nil || pod.Initializers.Result == nil || pod.Initializers.Result.Status != metav1.StatusFailure { + t.Fatalf("Pod returned from update was not correct: %#v", pod) + } +} + func TestNonGracefulStoreHandleFinalizers(t *testing.T) { initialGeneration := int64(1) podWithFinalizer := &example.Pod{ @@ -747,7 +987,7 @@ func TestNonGracefulStoreHandleFinalizers(t *testing.T) { registry.EnableGarbageCollection = true defer destroyFunc() // create pod - _, err := registry.Create(testContext, podWithFinalizer) + _, err := registry.Create(testContext, podWithFinalizer, false) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -1048,7 +1288,7 @@ func TestStoreDeleteWithOrphanDependents(t *testing.T) { for _, tc := range testcases { registry.DeleteStrategy = tc.strategy // create pod - _, err := registry.Create(testContext, tc.pod) + _, err := registry.Create(testContext, tc.pod, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -1267,7 +1507,7 @@ func TestStoreDeletionPropagation(t *testing.T) { i++ pod := createPod(i, tc.existingFinalizers) // create pod - _, err := registry.Create(testContext, pod) + _, err := registry.Create(testContext, pod, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -1311,10 +1551,10 @@ func TestStoreDeleteCollection(t *testing.T) { destroyFunc, registry := NewTestGenericStoreRegistry(t) defer destroyFunc() - if _, err := registry.Create(testContext, podA); err != nil { + if _, err := registry.Create(testContext, podA, false); err != nil { t.Errorf("Unexpected error: %v", err) } - if _, err := registry.Create(testContext, podB); err != nil { + if _, err := registry.Create(testContext, podB, false); err != nil { t.Errorf("Unexpected error: %v", err) } @@ -1347,10 +1587,10 @@ func TestStoreDeleteCollectionNotFound(t *testing.T) { for i := 0; i < 10; i++ { // Setup - if _, err := registry.Create(testContext, podA); err != nil { + if _, err := registry.Create(testContext, podA, false); err != nil { t.Errorf("Unexpected error: %v", err) } - if _, err := registry.Create(testContext, podB); err != nil { + if _, err := registry.Create(testContext, podB, false); err != nil { t.Errorf("Unexpected error: %v", err) } @@ -1386,7 +1626,7 @@ func TestStoreDeleteCollectionWithWatch(t *testing.T) { destroyFunc, registry := NewTestGenericStoreRegistry(t) defer destroyFunc() - objCreated, err := registry.Create(testContext, podA) + objCreated, err := registry.Create(testContext, podA, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -1455,7 +1695,7 @@ func TestStoreWatch(t *testing.T) { if err != nil { t.Errorf("%v: unexpected error: %v", name, err) } else { - obj, err := registry.Create(testContext, podA) + obj, err := registry.Create(testContext, podA, false) if err != nil { got, open := <-wi.ResultChan() if !open { @@ -1530,12 +1770,12 @@ func newTestGenericStoreRegistry(t *testing.T, scheme *runtime.Scheme, hasCacheE return storage.SelectionPredicate{ Label: label, Field: field, - GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod, ok := obj.(*example.Pod) if !ok { - return nil, nil, fmt.Errorf("not a pod") + return nil, nil, false, fmt.Errorf("not a pod") } - return labels.Set(pod.ObjectMeta.Labels), generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true), nil + return labels.Set(pod.ObjectMeta.Labels), generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true), pod.Initializers != nil, nil }, } }, diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go index 015754f860..4421ffcac3 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go @@ -174,8 +174,9 @@ type Creater interface { // This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object) New() runtime.Object - // Create creates a new version of a resource. - Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) + // Create creates a new version of a resource. If includeUninitialized is set, the object may be returned + // without completing initialization. + Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) } // NamedCreater is an object that can create an instance of a RESTful object using a name parameter. @@ -186,8 +187,9 @@ type NamedCreater interface { // Create creates a new version of a resource. It expects a name parameter from the path. // This is needed for create operations on subresources which include the name of the parent - // resource in the path. - Create(ctx genericapirequest.Context, name string, obj runtime.Object) (runtime.Object, error) + // resource in the path. If includeUninitialized is set, the object may be returned without + // completing initialization. + Create(ctx genericapirequest.Context, name string, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) } // UpdatedObjectInfo provides information about an updated object to an Updater. diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go index be2fb94fe3..f1e2a1bad1 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go @@ -251,7 +251,7 @@ func (t *Tester) testCreateAlreadyExisting(obj runtime.Object, createFn CreateFu } defer t.delete(ctx, foo) - _, err := t.storage.(rest.Creater).Create(ctx, foo) + _, err := t.storage.(rest.Creater).Create(ctx, foo, false) if !errors.IsAlreadyExists(err) { t.Errorf("expected already exists err, got %v", err) } @@ -263,7 +263,7 @@ func (t *Tester) testCreateEquals(obj runtime.Object, getFn GetFunc) { foo := copyOrDie(obj, t.scheme) t.setObjectMeta(foo, t.namer(2)) - created, err := t.storage.(rest.Creater).Create(ctx, foo) + created, err := t.storage.(rest.Creater).Create(ctx, foo, false) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -291,7 +291,7 @@ func (t *Tester) testCreateDiscardsObjectNamespace(valid runtime.Object) { objectMeta.SetNamespace("not-default") // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted - created, err := t.storage.(rest.Creater).Create(t.TestContext(), copyOrDie(valid, t.scheme)) + created, err := t.storage.(rest.Creater).Create(t.TestContext(), copyOrDie(valid, t.scheme), false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -307,7 +307,7 @@ func (t *Tester) testCreateGeneratesName(valid runtime.Object) { objectMeta.SetName("") objectMeta.SetGenerateName("test-") - created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid) + created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -322,7 +322,7 @@ func (t *Tester) testCreateHasMetadata(valid runtime.Object) { objectMeta.SetName(t.namer(1)) objectMeta.SetNamespace(t.TestNamespace()) - obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid) + obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -340,7 +340,7 @@ func (t *Tester) testCreateIgnoresContextNamespace(valid runtime.Object) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2") // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted - created, err := t.storage.(rest.Creater).Create(ctx, copyOrDie(valid, t.scheme)) + created, err := t.storage.(rest.Creater).Create(ctx, copyOrDie(valid, t.scheme), false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -359,7 +359,7 @@ func (t *Tester) testCreateIgnoresMismatchedNamespace(valid runtime.Object) { ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2") // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted - created, err := t.storage.(rest.Creater).Create(ctx, copyOrDie(valid, t.scheme)) + created, err := t.storage.(rest.Creater).Create(ctx, copyOrDie(valid, t.scheme), false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -377,7 +377,7 @@ func (t *Tester) testCreateValidatesNames(valid runtime.Object) { objCopyMeta.SetName(invalidName) ctx := t.TestContext() - _, err := t.storage.(rest.Creater).Create(ctx, objCopy) + _, err := t.storage.(rest.Creater).Create(ctx, objCopy, false) if !errors.IsInvalid(err) { t.Errorf("%s: Expected to get an invalid resource error, got '%v'", invalidName, err) } @@ -389,7 +389,7 @@ func (t *Tester) testCreateValidatesNames(valid runtime.Object) { objCopyMeta.SetName(objCopyMeta.GetName() + invalidSuffix) ctx := t.TestContext() - _, err := t.storage.(rest.Creater).Create(ctx, objCopy) + _, err := t.storage.(rest.Creater).Create(ctx, objCopy, false) if !errors.IsInvalid(err) { t.Errorf("%s: Expected to get an invalid resource error, got '%v'", invalidSuffix, err) } @@ -399,7 +399,7 @@ func (t *Tester) testCreateValidatesNames(valid runtime.Object) { func (t *Tester) testCreateInvokesValidation(invalid ...runtime.Object) { for i, obj := range invalid { ctx := t.TestContext() - _, err := t.storage.(rest.Creater).Create(ctx, obj) + _, err := t.storage.(rest.Creater).Create(ctx, obj, false) if !errors.IsInvalid(err) { t.Errorf("%d: Expected to get an invalid resource error, got %v", i, err) } @@ -410,7 +410,7 @@ func (t *Tester) testCreateRejectsMismatchedNamespace(valid runtime.Object) { objectMeta := t.getObjectMetaOrFail(valid) objectMeta.SetNamespace("not-default") - _, err := t.storage.(rest.Creater).Create(t.TestContext(), valid) + _, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false) if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if !strings.Contains(err.Error(), "does not match the namespace sent on the request") { @@ -424,7 +424,7 @@ func (t *Tester) testCreateResetsUserData(valid runtime.Object) { objectMeta.SetUID("bad-uid") objectMeta.SetCreationTimestamp(now) - obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid) + obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -442,7 +442,7 @@ func (t *Tester) testCreateIgnoreClusterName(valid runtime.Object) { objectMeta.SetName(t.namer(3)) objectMeta.SetClusterName("clustername-to-ignore") - obj, err := t.storage.(rest.Creater).Create(t.TestContext(), copyOrDie(valid, t.scheme)) + obj, err := t.storage.(rest.Creater).Create(t.TestContext(), copyOrDie(valid, t.scheme), false) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -1071,14 +1071,14 @@ func (t *Tester) testGetDifferentNamespace(obj runtime.Object) { ctx1 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar3") objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1)) - _, err := t.storage.(rest.Creater).Create(ctx1, obj) + _, err := t.storage.(rest.Creater).Create(ctx1, obj, false) if err != nil { t.Errorf("unexpected error: %v", err) } ctx2 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar4") objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx2)) - _, err = t.storage.(rest.Creater).Create(ctx2, obj) + _, err = t.storage.(rest.Creater).Create(ctx2, obj, false) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1112,7 +1112,7 @@ func (t *Tester) testGetFound(obj runtime.Object) { ctx := t.TestContext() t.setObjectMeta(obj, t.namer(1)) - existing, err := t.storage.(rest.Creater).Create(ctx, obj) + existing, err := t.storage.(rest.Creater).Create(ctx, obj, false) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1135,7 +1135,7 @@ func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) { objMeta := t.getObjectMetaOrFail(obj) objMeta.SetName(t.namer(4)) objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1)) - _, err := t.storage.(rest.Creater).Create(ctx1, obj) + _, err := t.storage.(rest.Creater).Create(ctx1, obj, false) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1154,7 +1154,7 @@ func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) { func (t *Tester) testGetNotFound(obj runtime.Object) { ctx := t.TestContext() t.setObjectMeta(obj, t.namer(2)) - _, err := t.storage.(rest.Creater).Create(ctx, obj) + _, err := t.storage.(rest.Creater).Create(ctx, obj, false) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/admission.go b/staging/src/k8s.io/apiserver/pkg/server/options/admission.go index 760f4fc3da..0a86aefa07 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/admission.go @@ -41,7 +41,7 @@ type AdmissionOptions struct { func NewAdmissionOptions() *AdmissionOptions { options := &AdmissionOptions{ Plugins: &admission.Plugins{}, - PluginNames: []string{}, + PluginNames: []string{"Initializers"}, } server.RegisterAllAdmissionPlugins(options.Plugins) return options diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher.go index ac5e016082..5cc9e93e15 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher.go @@ -62,8 +62,8 @@ type CacherConfig struct { // KeyFunc is used to get a key in the underlying storage for a given object. KeyFunc func(runtime.Object) (string, error) - // GetAttrsFunc is used to get object labels and fields. - GetAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error) + // GetAttrsFunc is used to get object labels, fields, and the uninitialized bool + GetAttrsFunc func(runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) // TriggerPublisherFunc is used for optimizing amount of watchers that // needs to process an incoming event. @@ -131,7 +131,7 @@ func (i *indexedWatchers) terminateAll(objectType reflect.Type) { } } -type watchFilterFunc func(string, labels.Set, fields.Set) bool +type watchFilterFunc func(key string, l labels.Set, f fields.Set, uninitialized bool) bool // Cacher is responsible for serving WATCH and LIST requests for a given // resource from its internal cache and updating its cache in the background @@ -658,11 +658,11 @@ func filterFunction(key string, p SelectionPredicate) func(string, runtime.Objec } func watchFilterFunction(key string, p SelectionPredicate) watchFilterFunc { - filterFunc := func(objKey string, label labels.Set, field fields.Set) bool { + filterFunc := func(objKey string, label labels.Set, field fields.Set, uninitialized bool) bool { if !hasPathPrefix(objKey, key) { return false } - return p.MatchesLabelsAndFields(label, field) + return p.MatchesObjectAttributes(label, field, uninitialized) } return filterFunc } @@ -840,10 +840,10 @@ func (c *cacheWatcher) add(event *watchCacheEvent, budget *timeBudget) { // NOTE: sendWatchCacheEvent is assumed to not modify !!! func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) { - curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields) + curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields, event.ObjUninitialized) oldObjPasses := false if event.PrevObject != nil { - oldObjPasses = c.filter(event.Key, event.PrevObjLabels, event.PrevObjFields) + oldObjPasses = c.filter(event.Key, event.PrevObjLabels, event.PrevObjFields, event.PrevObjUninitialized) } if !curObjPasses && !oldObjPasses { // Watcher is not interested in that object. diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher_whitebox_test.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher_whitebox_test.go index 9a80b5f0da..b8973d0b68 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher_whitebox_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher_whitebox_test.go @@ -37,7 +37,7 @@ import ( func TestCacheWatcherCleanupNotBlockedByResult(t *testing.T) { var lock sync.RWMutex count := 0 - filter := func(string, labels.Set, fields.Set) bool { return true } + filter := func(string, labels.Set, fields.Set, bool) bool { return true } forget := func(bool) { lock.Lock() defer lock.Unlock() @@ -61,7 +61,7 @@ func TestCacheWatcherCleanupNotBlockedByResult(t *testing.T) { } func TestCacheWatcherHandlesFiltering(t *testing.T) { - filter := func(_ string, _ labels.Set, field fields.Set) bool { + filter := func(_ string, _ labels.Set, field fields.Set, _ bool) bool { return field["spec.nodeName"] == "host" } forget := func(bool) {} diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd/etcd_helper_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd/etcd_helper_test.go index 9cc2967350..c6b046a4a7 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd/etcd_helper_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd/etcd_helper_test.go @@ -249,9 +249,9 @@ func TestListFiltered(t *testing.T) { p := storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.SelectorFromSet(fields.Set{"metadata.name": "bar"}), - GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod := obj.(*example.Pod) - return labels.Set(pod.Labels), fields.Set{"metadata.name": pod.Name}, nil + return labels.Set(pod.Labels), fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil }, } var got example.PodList diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go index 77f397e804..cc1d76c778 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go @@ -285,9 +285,9 @@ func TestGetToList(t *testing.T) { pred: storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.ParseSelectorOrDie("metadata.name!=" + storedObj.Name), - GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod := obj.(*example.Pod) - return nil, fields.Set{"metadata.name": pod.Name}, nil + return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil }, }, expectedOut: nil, @@ -644,9 +644,9 @@ func TestList(t *testing.T) { pred: storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.ParseSelectorOrDie("metadata.name!=" + preset[0].storedObj.Name), - GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod := obj.(*example.Pod) - return nil, fields.Set{"metadata.name": pod.Name}, nil + return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil }, }, expectedOut: nil, diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go index cb172564d3..50b4637102 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher.go @@ -118,7 +118,7 @@ func (w *watcher) createWatchChan(ctx context.Context, key string, rev int64, re resultChan: make(chan watch.Event, outgoingBufSize), errChan: make(chan error, 1), } - if pred.Label.Empty() && pred.Field.Empty() { + if pred.Empty() { // The filter doesn't filter out any object. wc.internalFilter = nil } diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher_test.go index 58cbfe4c29..052e4dc73a 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher_test.go @@ -73,9 +73,9 @@ func testWatch(t *testing.T, recursive bool) { pred: storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.ParseSelectorOrDie("metadata.name=bar"), - GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod := obj.(*example.Pod) - return nil, fields.Set{"metadata.name": pod.Name}, nil + return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil }, }, }, { // update @@ -88,9 +88,9 @@ func testWatch(t *testing.T, recursive bool) { pred: storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.ParseSelectorOrDie("metadata.name!=bar"), - GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod := obj.(*example.Pod) - return nil, fields.Set{"metadata.name": pod.Name}, nil + return nil, fields.Set{"metadata.name": pod.Name}, pod.Initializers != nil, nil }, }, }} diff --git a/staging/src/k8s.io/apiserver/pkg/storage/interfaces.go b/staging/src/k8s.io/apiserver/pkg/storage/interfaces.go index e8181c3e84..74abfdc3e0 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/interfaces.go @@ -72,6 +72,8 @@ type FilterFunc func(obj runtime.Object) bool var Everything = SelectionPredicate{ Label: labels.Everything(), Field: fields.Everything(), + // TODO: split this into a new top level constant? + IncludeUninitialized: true, } // Pass an UpdateFunc to Interface.GuaranteedUpdate to make an update diff --git a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go index 8878245d1f..3a345303b7 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go @@ -22,29 +22,33 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// AttrFunc returns label and field sets for List or Watch to match. +// AttrFunc returns label and field sets and the uninitialized flag for List or Watch to match. // In any failure to parse given object, it returns error. -type AttrFunc func(obj runtime.Object) (labels.Set, fields.Set, error) +type AttrFunc func(obj runtime.Object) (labels.Set, fields.Set, bool, error) // SelectionPredicate is used to represent the way to select objects from api storage. type SelectionPredicate struct { - Label labels.Selector - Field fields.Selector - GetAttrs AttrFunc - IndexFields []string + Label labels.Selector + Field fields.Selector + IncludeUninitialized bool + GetAttrs AttrFunc + IndexFields []string } // Matches returns true if the given object's labels and fields (as // returned by s.GetAttrs) match s.Label and s.Field. An error is // returned if s.GetAttrs fails. func (s *SelectionPredicate) Matches(obj runtime.Object) (bool, error) { - if s.Label.Empty() && s.Field.Empty() { + if s.Empty() { return true, nil } - labels, fields, err := s.GetAttrs(obj) + labels, fields, uninitialized, err := s.GetAttrs(obj) if err != nil { return false, err } + if !s.IncludeUninitialized && uninitialized { + return false, nil + } matched := s.Label.Matches(labels) if matched && s.Field != nil { matched = (matched && s.Field.Matches(fields)) @@ -52,9 +56,12 @@ func (s *SelectionPredicate) Matches(obj runtime.Object) (bool, error) { return matched, nil } -// MatchesLabelsAndFields returns true if the given labels and fields +// MatchesObjectAttributes returns true if the given labels and fields // match s.Label and s.Field. -func (s *SelectionPredicate) MatchesLabelsAndFields(l labels.Set, f fields.Set) bool { +func (s *SelectionPredicate) MatchesObjectAttributes(l labels.Set, f fields.Set, uninitialized bool) bool { + if !s.IncludeUninitialized && uninitialized { + return false + } if s.Label.Empty() && s.Field.Empty() { return true } @@ -93,10 +100,11 @@ func (s *SelectionPredicate) RemoveMatchesSingleRequirements() (SelectionPredica } } return SelectionPredicate{ - Label: s.Label, - Field: fieldsSelector, - GetAttrs: s.GetAttrs, - IndexFields: s.IndexFields, + Label: s.Label, + Field: fieldsSelector, + IncludeUninitialized: s.IncludeUninitialized, + GetAttrs: s.GetAttrs, + IndexFields: s.IndexFields, }, nil } @@ -113,3 +121,8 @@ func (s *SelectionPredicate) MatcherIndex() []MatchValue { } return result } + +// Empty returns true if the predicate performs no filtering. +func (s *SelectionPredicate) Empty() bool { + return s.Label.Empty() && s.Field.Empty() && s.IncludeUninitialized +} diff --git a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go index d51666e21b..3c5da649a6 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go @@ -42,6 +42,7 @@ func TestSelectionPredicate(t *testing.T) { labelSelector, fieldSelector string labels labels.Set fields fields.Set + uninitialized bool err error shouldMatch bool matchSingleKey string @@ -74,6 +75,14 @@ func TestSelectionPredicate(t *testing.T) { shouldMatch: true, matchSingleKey: "12345", }, + "E": { + fieldSelector: "metadata.name=12345", + labels: labels.Set{}, + fields: fields.Set{"metadata.name": "12345"}, + uninitialized: true, + shouldMatch: false, + matchSingleKey: "12345", + }, "error": { labelSelector: "name=foo", fieldSelector: "uid=12345", @@ -94,8 +103,8 @@ func TestSelectionPredicate(t *testing.T) { sp := &SelectionPredicate{ Label: parsedLabel, Field: parsedField, - GetAttrs: func(runtime.Object) (label labels.Set, field fields.Set, err error) { - return item.labels, item.fields, item.err + GetAttrs: func(runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) { + return item.labels, item.fields, item.uninitialized, item.err }, } got, err := sp.Matches(&Ignored{}) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/tests/cacher_test.go b/staging/src/k8s.io/apiserver/pkg/storage/tests/cacher_test.go index 619c3ea836..ef0c4f38de 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/tests/cacher_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/tests/cacher_test.go @@ -61,12 +61,12 @@ func init() { } // GetAttrs returns labels and fields of a given object for filtering purposes. -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { pod, ok := obj.(*example.Pod) if !ok { - return nil, nil, fmt.Errorf("not a pod") + return nil, nil, false, fmt.Errorf("not a pod") } - return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil + return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), pod.Initializers != nil, nil } // PodToSelectableFields returns a field set that represents the object @@ -469,12 +469,12 @@ func TestFiltering(t *testing.T) { pred := storage.SelectionPredicate{ Label: labels.SelectorFromSet(labels.Set{"filter": "foo"}), Field: fields.Everything(), - GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, err error) { + GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) { metadata, err := meta.Accessor(obj) if err != nil { t.Fatalf("Unexpected error: %v", err) } - return labels.Set(metadata.GetLabels()), nil, nil + return labels.Set(metadata.GetLabels()), nil, metadata.GetInitializers() != nil, nil }, } watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, pred) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go b/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go index beca63488d..1268b9d7a5 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go @@ -47,15 +47,17 @@ const ( // the previous value of the object to enable proper filtering in the // upper layers. type watchCacheEvent struct { - Type watch.EventType - Object runtime.Object - ObjLabels labels.Set - ObjFields fields.Set - PrevObject runtime.Object - PrevObjLabels labels.Set - PrevObjFields fields.Set - Key string - ResourceVersion uint64 + Type watch.EventType + Object runtime.Object + ObjLabels labels.Set + ObjFields fields.Set + ObjUninitialized bool + PrevObject runtime.Object + PrevObjLabels labels.Set + PrevObjFields fields.Set + PrevObjUninitialized bool + Key string + ResourceVersion uint64 } // Computing a key of an object is generally non-trivial (it performs @@ -102,7 +104,7 @@ type watchCache struct { keyFunc func(runtime.Object) (string, error) // getAttrsFunc is used to get labels and fields of an object. - getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error) + getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, bool, error) // cache is used a cyclic buffer - its first element (with the smallest // resourceVersion) is defined by startIndex, its last element is defined @@ -136,7 +138,7 @@ type watchCache struct { func newWatchCache( capacity int, keyFunc func(runtime.Object) (string, error), - getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error)) *watchCache { + getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, bool, error)) *watchCache { wc := &watchCache{ capacity: capacity, keyFunc: keyFunc, @@ -229,30 +231,33 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd if err != nil { return err } - objLabels, objFields, err := w.getAttrsFunc(event.Object) + objLabels, objFields, objUninitialized, err := w.getAttrsFunc(event.Object) if err != nil { return err } var prevObject runtime.Object var prevObjLabels labels.Set var prevObjFields fields.Set + var prevObjUninitialized bool if exists { prevObject = previous.(*storeElement).Object - prevObjLabels, prevObjFields, err = w.getAttrsFunc(prevObject) + prevObjLabels, prevObjFields, prevObjUninitialized, err = w.getAttrsFunc(prevObject) if err != nil { return err } } watchCacheEvent := &watchCacheEvent{ - Type: event.Type, - Object: event.Object, - ObjLabels: objLabels, - ObjFields: objFields, - PrevObject: prevObject, - PrevObjLabels: prevObjLabels, - PrevObjFields: prevObjFields, - Key: key, - ResourceVersion: resourceVersion, + Type: event.Type, + Object: event.Object, + ObjLabels: objLabels, + ObjFields: objFields, + ObjUninitialized: objUninitialized, + PrevObject: prevObject, + PrevObjLabels: prevObjLabels, + PrevObjFields: prevObjFields, + PrevObjUninitialized: prevObjUninitialized, + Key: key, + ResourceVersion: resourceVersion, } if w.onEvent != nil { w.onEvent(watchCacheEvent) @@ -425,17 +430,18 @@ func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*w if !ok { return nil, fmt.Errorf("not a storeElement: %v", elem) } - objLabels, objFields, err := w.getAttrsFunc(elem.Object) + objLabels, objFields, objUninitialized, err := w.getAttrsFunc(elem.Object) if err != nil { return nil, err } result[i] = &watchCacheEvent{ - Type: watch.Added, - Object: elem.Object, - ObjLabels: objLabels, - ObjFields: objFields, - Key: elem.Key, - ResourceVersion: w.resourceVersion, + Type: watch.Added, + Object: elem.Object, + ObjLabels: objLabels, + ObjFields: objFields, + ObjUninitialized: objUninitialized, + Key: elem.Key, + ResourceVersion: w.resourceVersion, } } return result, nil diff --git a/staging/src/k8s.io/apiserver/pkg/storage/watch_cache_test.go b/staging/src/k8s.io/apiserver/pkg/storage/watch_cache_test.go index 744326d5ba..9f8a360e00 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/watch_cache_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/watch_cache_test.go @@ -50,8 +50,8 @@ func newTestWatchCache(capacity int) *watchCache { keyFunc := func(obj runtime.Object) (string, error) { return NamespaceKeyFunc("prefix", obj) } - getAttrsFunc := func(obj runtime.Object) (labels.Set, fields.Set, error) { - return nil, nil, nil + getAttrsFunc := func(obj runtime.Object) (labels.Set, fields.Set, bool, error) { + return nil, nil, false, nil } wc := newWatchCache(capacity, keyFunc, getAttrsFunc) wc.clock = clock.NewFakeClock(time.Now()) diff --git a/staging/src/k8s.io/client-go/Godeps/Godeps.json b/staging/src/k8s.io/client-go/Godeps/Godeps.json index d6b84408d6..a2bbefe1b7 100644 --- a/staging/src/k8s.io/client-go/Godeps/Godeps.json +++ b/staging/src/k8s.io/client-go/Godeps/Godeps.json @@ -342,6 +342,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/fields", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" diff --git a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go index a07d543942..fdcb6637f7 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go @@ -113,12 +113,12 @@ func (apiServerStatusStrategy) ValidateUpdate(ctx genericapirequest.Context, obj return validation.ValidateAPIServiceStatusUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) } -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { apiserver, ok := obj.(*apiregistration.APIService) if !ok { - return nil, nil, fmt.Errorf("given object is not a APIService.") + return nil, nil, false, fmt.Errorf("given object is not a APIService.") } - return labels.Set(apiserver.ObjectMeta.Labels), APIServiceToSelectableFields(apiserver), nil + return labels.Set(apiserver.ObjectMeta.Labels), APIServiceToSelectableFields(apiserver), apiserver.Initializers != nil, nil } // MatchAPIService is the filter used by the generic etcd backend to watch events diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/strategy.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/strategy.go index c2b739f3ce..bafe064ac1 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/strategy.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/strategy.go @@ -83,12 +83,12 @@ func (CustomResourceDefinitionStorageStrategy) ValidateUpdate(ctx genericapirequ return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata")) } -func (a CustomResourceDefinitionStorageStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func (a CustomResourceDefinitionStorageStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { accessor, err := meta.Accessor(obj) if err != nil { - return nil, nil, err + return nil, nil, false, err } - return labels.Set(accessor.GetLabels()), objectMetaFieldsSet(accessor, a.namespaceScoped), nil + return labels.Set(accessor.GetLabels()), objectMetaFieldsSet(accessor, a.namespaceScoped), accessor.GetInitializers() != nil, nil } // objectMetaFieldsSet returns a fields that represent the ObjectMeta. diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go index 07e77c5204..e802102bbe 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go @@ -107,12 +107,12 @@ func (statusStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old run return validation.ValidateUpdateCustomResourceDefinitionStatus(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition)) } -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { apiserver, ok := obj.(*apiextensions.CustomResourceDefinition) if !ok { - return nil, nil, fmt.Errorf("given object is not a CustomResourceDefinition.") + return nil, nil, false, fmt.Errorf("given object is not a CustomResourceDefinition.") } - return labels.Set(apiserver.ObjectMeta.Labels), CustomResourceDefinitionToSelectableFields(apiserver), nil + return labels.Set(apiserver.ObjectMeta.Labels), CustomResourceDefinitionToSelectableFields(apiserver), apiserver.Initializers != nil, nil } // MatchCustomResourceDefinition is the filter used by the generic etcd backend to watch events diff --git a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/strategy.go b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/strategy.go index 12b1a6a817..24a4cb16cb 100644 --- a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/strategy.go +++ b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/strategy.go @@ -71,12 +71,12 @@ func (apiServerStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old // return validation.ValidateFlunderUpdate(obj.(*wardle.Flunder), old.(*wardle.Flunder)) } -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { apiserver, ok := obj.(*wardle.Flunder) if !ok { - return nil, nil, fmt.Errorf("given object is not a Flunder.") + return nil, nil, false, fmt.Errorf("given object is not a Flunder.") } - return labels.Set(apiserver.ObjectMeta.Labels), FlunderToSelectableFields(apiserver), nil + return labels.Set(apiserver.ObjectMeta.Labels), FlunderToSelectableFields(apiserver), apiserver.Initializers != nil, nil } // MatchFlunder is the filter used by the generic etcd backend to watch events diff --git a/test/e2e/BUILD b/test/e2e/BUILD index fbe5d4cfb9..70d51158c1 100644 --- a/test/e2e/BUILD +++ b/test/e2e/BUILD @@ -23,6 +23,7 @@ go_test( "//pkg/metrics:go_default_library", "//test/e2e/autoscaling:go_default_library", "//test/e2e/cluster-logging:go_default_library", + "//test/e2e/extension:go_default_library", "//test/e2e/framework:go_default_library", "//test/e2e/perf:go_default_library", "//test/e2e/scheduling:go_default_library", @@ -235,6 +236,7 @@ filegroup( "//test/e2e/chaosmonkey:all-srcs", "//test/e2e/cluster-logging:all-srcs", "//test/e2e/common:all-srcs", + "//test/e2e/extension:all-srcs", "//test/e2e/framework:all-srcs", "//test/e2e/generated:all-srcs", "//test/e2e/perf:all-srcs", diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 0d38276369..fe4672e905 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -21,6 +21,7 @@ import ( _ "k8s.io/kubernetes/test/e2e/autoscaling" _ "k8s.io/kubernetes/test/e2e/cluster-logging" + _ "k8s.io/kubernetes/test/e2e/extension" "k8s.io/kubernetes/test/e2e/framework" _ "k8s.io/kubernetes/test/e2e/perf" _ "k8s.io/kubernetes/test/e2e/scheduling" diff --git a/test/e2e/extension/BUILD b/test/e2e/extension/BUILD new file mode 100644 index 0000000000..bc9bf9064a --- /dev/null +++ b/test/e2e/extension/BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["initializers.go"], + tags = ["automanaged"], + deps = [ + "//pkg/api/v1:go_default_library", + "//test/e2e/framework:go_default_library", + "//vendor/github.com/onsi/ginkgo:go_default_library", + "//vendor/github.com/onsi/gomega:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/test/e2e/extension/initializers.go b/test/e2e/extension/initializers.go new file mode 100644 index 0000000000..49922ac2b3 --- /dev/null +++ b/test/e2e/extension/initializers.go @@ -0,0 +1,121 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package extension + +import ( + "fmt" + "strings" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/test/e2e/framework" +) + +var _ = framework.KubeDescribe("Initializers", func() { + f := framework.NewDefaultFramework("initializers") + + // TODO: Add failure traps once we have JustAfterEach + // See https://github.com/onsi/ginkgo/issues/303 + + It("should be invisible to controllers by default", func() { + ns := f.Namespace.Name + c := f.ClientSet + + podName := "uninitialized-pod" + framework.Logf("Creating pod %s", podName) + + ch := make(chan struct{}) + go func() { + _, err := c.Core().Pods(ns).Create(newUninitializedPod(podName)) + Expect(err).NotTo(HaveOccurred()) + close(ch) + }() + + // wait to ensure the scheduler does not act on an uninitialized pod + err := wait.PollImmediate(2*time.Second, 15*time.Second, func() (bool, error) { + p, err := c.Core().Pods(ns).Get(podName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + return len(p.Spec.NodeName) > 0, nil + }) + Expect(err).To(Equal(wait.ErrWaitTimeout)) + + // verify that we can update an initializing pod + pod, err := c.Core().Pods(ns).Get(podName, metav1.GetOptions{}) + pod.Annotations = map[string]string{"update-1": "test"} + pod, err = c.Core().Pods(ns).Update(pod) + Expect(err).NotTo(HaveOccurred()) + + // clear initializers + pod.Initializers = nil + pod, err = c.Core().Pods(ns).Update(pod) + Expect(err).NotTo(HaveOccurred()) + + // pod should now start running + err = framework.WaitForPodRunningInNamespace(c, pod) + Expect(err).NotTo(HaveOccurred()) + + // ensure create call returns + <-ch + + // verify that we cannot start the pod initializing again + pod, err = c.Core().Pods(ns).Get(podName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + pod.Initializers = &metav1.Initializers{ + Pending: []metav1.Initializer{{Name: "Other"}}, + } + _, err = c.Core().Pods(ns).Update(pod) + if !errors.IsInvalid(err) || !strings.Contains(err.Error(), "immutable") { + Fail(fmt.Sprintf("expected invalid error: %v", err)) + } + }) + +}) + +func newUninitializedPod(podName string) *v1.Pod { + containerName := fmt.Sprintf("%s-container", podName) + port := 8080 + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Initializers: &metav1.Initializers{ + Pending: []metav1.Initializer{{Name: "Test"}}, + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: containerName, + Image: "gcr.io/google_containers/porter:4524579c0eb935c056c8e75563b4e1eda31587e0", + Env: []v1.EnvVar{{Name: fmt.Sprintf("SERVE_PORT_%d", port), Value: "foo"}}, + Ports: []v1.ContainerPort{{ContainerPort: int32(port)}}, + }, + }, + RestartPolicy: v1.RestartPolicyNever, + }, + } + return pod +} diff --git a/test/fixtures/doc-yaml/admin/high-availability/kube-apiserver.yaml b/test/fixtures/doc-yaml/admin/high-availability/kube-apiserver.yaml index 33d5cff5cd..6150aa737b 100644 --- a/test/fixtures/doc-yaml/admin/high-availability/kube-apiserver.yaml +++ b/test/fixtures/doc-yaml/admin/high-availability/kube-apiserver.yaml @@ -11,7 +11,7 @@ spec: - /bin/sh - -c - /usr/local/bin/kube-apiserver --address=127.0.0.1 --etcd-servers=http://127.0.0.1:4001 - --cloud-provider=gce --admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota + --cloud-provider=gce --admission-control=Initializers,NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota --service-cluster-ip-range=10.0.0.0/16 --client-ca-file=/srv/kubernetes/ca.crt --basic-auth-file=/srv/kubernetes/basic_auth.csv --cluster-name=e2e-test-bburns --tls-cert-file=/srv/kubernetes/server.cert --tls-private-key-file=/srv/kubernetes/server.key diff --git a/test/fixtures/doc-yaml/getting-started-guides/coreos/cloud-configs/master.yaml b/test/fixtures/doc-yaml/getting-started-guides/coreos/cloud-configs/master.yaml index be82a97f24..bc1ee220e5 100644 --- a/test/fixtures/doc-yaml/getting-started-guides/coreos/cloud-configs/master.yaml +++ b/test/fixtures/doc-yaml/getting-started-guides/coreos/cloud-configs/master.yaml @@ -91,7 +91,7 @@ coreos: ExecStart=/opt/bin/kube-apiserver \ --service-account-key-file=/opt/bin/kube-serviceaccount.key \ --service-account-lookup=true \ - --admission-control=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota \ + --admission-control=Initializers,NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota \ --runtime-config=api/v1 \ --allow-privileged=true \ --insecure-bind-address=0.0.0.0 \ diff --git a/test/kubemark/start-kubemark.sh b/test/kubemark/start-kubemark.sh index f64f48620b..06ae0818b7 100755 --- a/test/kubemark/start-kubemark.sh +++ b/test/kubemark/start-kubemark.sh @@ -70,7 +70,7 @@ SCHEDULER_TEST_ARGS="${SCHEDULER_TEST_ARGS:-}" APISERVER_TEST_ARGS="${APISERVER_TEST_ARGS:-}" STORAGE_BACKEND="${STORAGE_BACKEND:-}" NUM_NODES="${NUM_NODES:-}" -CUSTOM_ADMISSION_PLUGINS="${CUSTOM_ADMISSION_PLUGINS:-NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota}" +CUSTOM_ADMISSION_PLUGINS="${CUSTOM_ADMISSION_PLUGINS:-Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota}" EOF echo "Created the environment file for master." }