finish removal of exportoptions

This commit is contained in:
David Eads 2021-01-22 09:21:56 -05:00
parent 82ebcd1719
commit 37cc89ed8d
38 changed files with 88 additions and 655 deletions

View File

@ -167,7 +167,7 @@ var nonRoundTrippableTypes = sets.NewString(
"PatchOptions", "PatchOptions",
) )
var commonKinds = []string{"Status", "ListOptions", "DeleteOptions", "ExportOptions", "GetOptions", "CreateOptions", "UpdateOptions", "PatchOptions"} var commonKinds = []string{"Status", "ListOptions", "DeleteOptions", "GetOptions", "CreateOptions", "UpdateOptions", "PatchOptions"}
// TestCommonKindsRegistered verifies that all group/versions registered with // TestCommonKindsRegistered verifies that all group/versions registered with
// the legacyscheme package have the common kinds. // the legacyscheme package have the common kinds.

View File

@ -64,7 +64,6 @@ var typesAllowedTags = map[reflect.Type]bool{
reflect.TypeOf(metav1.OwnerReference{}): true, reflect.TypeOf(metav1.OwnerReference{}): true,
reflect.TypeOf(metav1.LabelSelector{}): true, reflect.TypeOf(metav1.LabelSelector{}): true,
reflect.TypeOf(metav1.GetOptions{}): true, reflect.TypeOf(metav1.GetOptions{}): true,
reflect.TypeOf(metav1.ExportOptions{}): true,
reflect.TypeOf(metav1.ListOptions{}): true, reflect.TypeOf(metav1.ListOptions{}): true,
reflect.TypeOf(metav1.DeleteOptions{}): true, reflect.TypeOf(metav1.DeleteOptions{}): true,
reflect.TypeOf(metav1.GroupVersionKind{}): true, reflect.TypeOf(metav1.GroupVersionKind{}): true,

View File

@ -160,18 +160,7 @@ func TestLegacyRestStorageStrategies(t *testing.T) {
t.Errorf("failed to create legacy REST storage: %v", err) t.Errorf("failed to create legacy REST storage: %v", err)
} }
// Any new stores with export logic will need to be added here: strategyErrors := registrytest.ValidateStorageStrategies(apiGroupInfo.VersionedResourcesStorageMap["v1"])
exceptions := registrytest.StrategyExceptions{
// Only these stores should have an export strategy defined:
HasExportStrategy: []string{
"secrets",
"limitRanges",
"nodes",
"podTemplates",
},
}
strategyErrors := registrytest.ValidateStorageStrategies(apiGroupInfo.VersionedResourcesStorageMap["v1"], exceptions)
for _, err := range strategyErrors { for _, err := range strategyErrors {
t.Error(err) t.Error(err)
} }
@ -187,14 +176,8 @@ func TestCertificatesRestStorageStrategies(t *testing.T) {
t.Fatalf("unexpected error from REST storage: %v", err) t.Fatalf("unexpected error from REST storage: %v", err)
} }
exceptions := registrytest.StrategyExceptions{
HasExportStrategy: []string{
"certificatesigningrequests",
},
}
strategyErrors := registrytest.ValidateStorageStrategies( strategyErrors := registrytest.ValidateStorageStrategies(
apiGroupInfo.VersionedResourcesStorageMap[certificatesapiv1beta1.SchemeGroupVersion.Version], exceptions) apiGroupInfo.VersionedResourcesStorageMap[certificatesapiv1beta1.SchemeGroupVersion.Version])
for _, err := range strategyErrors { for _, err := range strategyErrors {
t.Error(err) t.Error(err)
} }

View File

@ -46,7 +46,6 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Approva
CreateStrategy: csrregistry.Strategy, CreateStrategy: csrregistry.Strategy,
UpdateStrategy: csrregistry.Strategy, UpdateStrategy: csrregistry.Strategy,
DeleteStrategy: csrregistry.Strategy, DeleteStrategy: csrregistry.Strategy,
ExportStrategy: csrregistry.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
} }

View File

@ -118,21 +118,6 @@ func (csrStrategy) AllowUnconditionalUpdate() bool {
return true return true
} }
func (s csrStrategy) Export(ctx context.Context, obj runtime.Object, exact bool) error {
csr, ok := obj.(*certificates.CertificateSigningRequest)
if !ok {
// unexpected programmer error
return fmt.Errorf("unexpected object: %v", obj)
}
s.PrepareForCreate(ctx, obj)
if exact {
return nil
}
// CSRs allow direct subresource edits, we clear them without exact so the CSR value can be reused.
csr.Status = certificates.CertificateSigningRequestStatus{}
return nil
}
// Storage strategy for the Status subresource // Storage strategy for the Status subresource
type csrStatusStrategy struct { type csrStatusStrategy struct {
csrStrategy csrStrategy

View File

@ -40,7 +40,6 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
CreateStrategy: limitrange.Strategy, CreateStrategy: limitrange.Strategy,
UpdateStrategy: limitrange.Strategy, UpdateStrategy: limitrange.Strategy,
DeleteStrategy: limitrange.Strategy, DeleteStrategy: limitrange.Strategy,
ExportStrategy: limitrange.Strategy,
// TODO: define table converter that exposes more than name/creation timestamp // TODO: define table converter that exposes more than name/creation timestamp
TableConvertor: rest.NewDefaultTableConvertor(api.Resource("limitranges")), TableConvertor: rest.NewDefaultTableConvertor(api.Resource("limitranges")),

View File

@ -72,10 +72,3 @@ func (limitrangeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O
func (limitrangeStrategy) AllowUnconditionalUpdate() bool { func (limitrangeStrategy) AllowUnconditionalUpdate() bool {
return true return true
} }
func (limitrangeStrategy) Export(context.Context, runtime.Object, bool) error {
// Copied from OpenShift exporter
// TODO: this needs to be fixed
// limitrange.Strategy.PrepareForCreate(ctx, obj)
return nil
}

View File

@ -119,10 +119,6 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio
return r.store.Watch(ctx, options) return r.store.Watch(ctx, options)
} }
func (r *REST) Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error) {
return r.store.Export(ctx, name, opts)
}
// Delete enforces life-cycle rules for namespace termination // Delete enforces life-cycle rules for namespace termination
func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
nsObj, err := r.Get(ctx, name, &metav1.GetOptions{}) nsObj, err := r.Get(ctx, name, &metav1.GetOptions{})

View File

@ -88,7 +88,6 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, kubeletClientConfig client
CreateStrategy: node.Strategy, CreateStrategy: node.Strategy,
UpdateStrategy: node.Strategy, UpdateStrategy: node.Strategy,
DeleteStrategy: node.Strategy, DeleteStrategy: node.Strategy,
ExportStrategy: node.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
} }

View File

@ -141,22 +141,6 @@ func (nodeStrategy) AllowUnconditionalUpdate() bool {
return true return true
} }
func (ns nodeStrategy) Export(ctx context.Context, obj runtime.Object, exact bool) error {
n, ok := obj.(*api.Node)
if !ok {
// unexpected programmer error
return fmt.Errorf("unexpected object: %v", obj)
}
ns.PrepareForCreate(ctx, obj)
if exact {
return nil
}
// Nodes are the only resources that allow direct status edits, therefore
// we clear that without exact so that the node value can be reused.
n.Status = api.NodeStatus{}
return nil
}
type nodeStatusStrategy struct { type nodeStatusStrategy struct {
nodeStrategy nodeStrategy
} }

View File

@ -42,7 +42,6 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
CreateStrategy: podtemplate.Strategy, CreateStrategy: podtemplate.Strategy,
UpdateStrategy: podtemplate.Strategy, UpdateStrategy: podtemplate.Strategy,
DeleteStrategy: podtemplate.Strategy, DeleteStrategy: podtemplate.Strategy,
ExportStrategy: podtemplate.Strategy,
ReturnDeletedObject: true, ReturnDeletedObject: true,

View File

@ -87,8 +87,3 @@ func (podTemplateStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.
func (podTemplateStrategy) AllowUnconditionalUpdate() bool { func (podTemplateStrategy) AllowUnconditionalUpdate() bool {
return true return true
} }
func (podTemplateStrategy) Export(ctx context.Context, obj runtime.Object, exact bool) error {
// Do nothing
return nil
}

View File

@ -17,7 +17,6 @@ go_library(
"//pkg/api/legacyscheme:go_default_library", "//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/validation:go_default_library", "//pkg/apis/core/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
@ -37,9 +36,6 @@ go_test(
"//pkg/api/testing:go_default_library", "//pkg/api/testing:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/install:go_default_library", "//pkg/apis/core/install:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
], ],
) )

View File

@ -44,7 +44,6 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
CreateStrategy: secret.Strategy, CreateStrategy: secret.Strategy,
UpdateStrategy: secret.Strategy, UpdateStrategy: secret.Strategy,
DeleteStrategy: secret.Strategy, DeleteStrategy: secret.Strategy,
ExportStrategy: secret.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
} }

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -85,26 +84,6 @@ func (strategy) AllowUnconditionalUpdate() bool {
return true return true
} }
func (s strategy) Export(ctx context.Context, obj runtime.Object, exact bool) error {
t, ok := obj.(*api.Secret)
if !ok {
// unexpected programmer error
return fmt.Errorf("unexpected object: %v", obj)
}
s.PrepareForCreate(ctx, obj)
if exact {
return nil
}
// secrets that are tied to the UID of a service account cannot be exported anyway
if t.Type == api.SecretTypeServiceAccountToken || len(t.Annotations[api.ServiceAccountUIDKey]) > 0 {
errs := []*field.Error{
field.Invalid(field.NewPath("type"), t, "can not export service account secrets"),
}
return errors.NewInvalid(api.Kind("Secret"), t.Name, errs)
}
return nil
}
// GetAttrs returns labels and fields of a given object for filtering purposes. // 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, error) {
secret, ok := obj.(*api.Secret) secret, ok := obj.(*api.Secret)

View File

@ -17,12 +17,8 @@ limitations under the License.
package secret package secret
import ( import (
"reflect"
"testing" "testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
apitesting "k8s.io/kubernetes/pkg/api/testing" apitesting "k8s.io/kubernetes/pkg/api/testing"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
@ -30,80 +26,6 @@ import (
_ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/core/install"
) )
func TestExportSecret(t *testing.T) {
tests := []struct {
objIn runtime.Object
objOut runtime.Object
exact bool
expectErr bool
}{
{
objIn: &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Data: map[string][]byte{
"foo": []byte("bar"),
},
},
objOut: &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Data: map[string][]byte{
"foo": []byte("bar"),
},
},
exact: true,
},
{
objIn: &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Type: api.SecretTypeServiceAccountToken,
},
expectErr: true,
},
{
objIn: &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
Annotations: map[string]string{
api.ServiceAccountUIDKey: "true",
},
},
},
expectErr: true,
},
{
objIn: &api.Pod{},
expectErr: true,
},
}
for _, test := range tests {
err := Strategy.Export(genericapirequest.NewContext(), test.objIn, test.exact)
if err != nil {
if !test.expectErr {
t.Errorf("unexpected error: %v", err)
}
continue
}
if test.expectErr {
t.Error("unexpected non-error")
continue
}
if !reflect.DeepEqual(test.objIn, test.objOut) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", test.objOut, test.objIn)
}
}
}
func TestSelectableFieldLabelConversions(t *testing.T) { func TestSelectableFieldLabelConversions(t *testing.T) {
apitesting.TestSelectableFieldLabelConversionsOfKind(t, apitesting.TestSelectableFieldLabelConversionsOfKind(t,
"v1", "v1",

View File

@ -79,7 +79,6 @@ type ServiceStorage interface {
rest.CreaterUpdater rest.CreaterUpdater
rest.GracefulDeleter rest.GracefulDeleter
rest.Watcher rest.Watcher
rest.Exporter
rest.StorageVersionProvider rest.StorageVersionProvider
} }
@ -189,10 +188,6 @@ func (rs *REST) Watch(ctx context.Context, options *metainternalversion.ListOpti
return rs.services.Watch(ctx, options) return rs.services.Watch(ctx, options)
} }
func (rs *REST) Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error) {
return rs.services.Export(ctx, name, opts)
}
func (rs *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { func (rs *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
service := obj.(*api.Service) service := obj.(*api.Service)

View File

@ -163,10 +163,6 @@ func (s *serviceStorage) ConvertToTable(ctx context.Context, object runtime.Obje
panic("not implemented") panic("not implemented")
} }
func (s *serviceStorage) Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error) {
panic("not implemented")
}
func (s *serviceStorage) StorageVersion() runtime.GroupVersioner { func (s *serviceStorage) StorageVersion() runtime.GroupVersioner {
panic("not implemented") panic("not implemented")
} }

View File

@ -57,7 +57,6 @@ func NewGenericREST(optsGetter generic.RESTOptionsGetter, serviceCIDR net.IPNet,
CreateStrategy: strategy, CreateStrategy: strategy,
UpdateStrategy: strategy, UpdateStrategy: strategy,
DeleteStrategy: strategy, DeleteStrategy: strategy,
ExportStrategy: strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
} }

View File

@ -18,7 +18,6 @@ package service
import ( import (
"context" "context"
"fmt"
"net" "net"
"reflect" "reflect"
@ -37,7 +36,6 @@ import (
type Strategy interface { type Strategy interface {
rest.RESTCreateUpdateStrategy rest.RESTCreateUpdateStrategy
rest.RESTExportStrategy
} }
// svcStrategy implements behavior for Services // svcStrategy implements behavior for Services
@ -139,30 +137,6 @@ func (svcStrategy) AllowUnconditionalUpdate() bool {
return true return true
} }
func (svcStrategy) Export(ctx context.Context, obj runtime.Object, exact bool) error {
t, ok := obj.(*api.Service)
if !ok {
// unexpected programmer error
return fmt.Errorf("unexpected object: %v", obj)
}
// TODO: service does not yet have a prepare create strategy (see above)
t.Status = api.ServiceStatus{}
if exact {
return nil
}
//set ClusterIPs as nil - if ClusterIPs[0] != None
if len(t.Spec.ClusterIPs) > 0 && t.Spec.ClusterIPs[0] != api.ClusterIPNone {
t.Spec.ClusterIP = ""
t.Spec.ClusterIPs = nil
}
if t.Spec.Type == api.ServiceTypeNodePort {
for i := range t.Spec.Ports {
t.Spec.Ports[i].NodePort = 0
}
}
return nil
}
// dropServiceDisabledFields drops fields that are not used if their associated feature gates // dropServiceDisabledFields drops fields that are not used if their associated feature gates
// are not enabled. The typical pattern is: // are not enabled. The typical pattern is:
// if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) { // if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) {

View File

@ -47,115 +47,6 @@ func newStrategy(cidr string, hasSecondary bool) (testStrategy Strategy, testSta
return return
} }
func TestExportService(t *testing.T) {
testStrategy, _ := newStrategy("10.0.0.0/16", false)
tests := []struct {
objIn runtime.Object
objOut runtime.Object
exact bool
expectErr bool
}{
{
objIn: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Status: api.ServiceStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "1.2.3.4"},
},
},
},
},
objOut: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
},
exact: true,
},
{
objIn: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: api.ServiceSpec{
ClusterIPs: []string{"10.0.0.1"},
},
Status: api.ServiceStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "1.2.3.4"},
},
},
},
},
objOut: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: api.ServiceSpec{
ClusterIPs: nil,
},
},
},
{
objIn: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: api.ServiceSpec{
ClusterIPs: []string{"10.0.0.1", "2001::1"},
},
Status: api.ServiceStatus{
LoadBalancer: api.LoadBalancerStatus{
Ingress: []api.LoadBalancerIngress{
{IP: "1.2.3.4"},
},
},
},
},
objOut: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: api.ServiceSpec{
ClusterIPs: nil,
},
},
},
{
objIn: &api.Pod{},
expectErr: true,
},
}
for _, test := range tests {
err := testStrategy.Export(genericapirequest.NewContext(), test.objIn, test.exact)
if err != nil {
if !test.expectErr {
t.Errorf("unexpected error: %v", err)
}
continue
}
if test.expectErr {
t.Error("unexpected non-error")
continue
}
if !reflect.DeepEqual(test.objIn, test.objOut) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", test.objOut, test.objIn)
}
}
}
func TestCheckGeneratedNameError(t *testing.T) { func TestCheckGeneratedNameError(t *testing.T) {
testStrategy, _ := newStrategy("10.0.0.0/16", false) testStrategy, _ := newStrategy("10.0.0.0/16", false)
expect := errors.NewNotFound(api.Resource("foos"), "bar") expect := errors.NewNotFound(api.Resource("foos"), "bar")

View File

@ -21,7 +21,6 @@ go_library(
deps = [ deps = [
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/kubeapiserver:go_default_library", "//pkg/kubeapiserver:go_default_library",
"//pkg/util/slice:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -117,10 +117,3 @@ func (r *ServiceRegistry) WatchServices(ctx context.Context, options *metaintern
return nil, r.Err return nil, r.Err
} }
func (r *ServiceRegistry) ExportService(ctx context.Context, name string, options metav1.ExportOptions) (*api.Service, error) {
r.mu.Lock()
defer r.mu.Unlock()
return r.Service, r.Err
}

View File

@ -21,17 +21,13 @@ import (
"k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/util/slice"
) )
// ValidateStorageStrategies ensures any instances of the generic registry.Store in the given storage map // ValidateStorageStrategies ensures any instances of the generic registry.Store in the given storage map
// have expected strategies defined. // have expected strategies defined.
func ValidateStorageStrategies(storageMap map[string]rest.Storage, exceptions StrategyExceptions) []error { func ValidateStorageStrategies(storageMap map[string]rest.Storage) []error {
errs := []error{} errs := []error{}
// Used to ensure we saw all the expected exceptions:
hasExportExceptionsSeen := []string{}
for k, storage := range storageMap { for k, storage := range storageMap {
switch t := storage.(type) { switch t := storage.(type) {
case registry.GenericStore: case registry.GenericStore:
@ -46,27 +42,6 @@ func ValidateStorageStrategies(storageMap map[string]rest.Storage, exceptions St
if t.GetDeleteStrategy() == nil { if t.GetDeleteStrategy() == nil {
errs = append(errs, fmt.Errorf("store for type [%v] does not have a DeleteStrategy", k)) errs = append(errs, fmt.Errorf("store for type [%v] does not have a DeleteStrategy", k))
} }
// Check that ExportStrategy is set if applicable:
if slice.ContainsString(exceptions.HasExportStrategy, k, nil) {
hasExportExceptionsSeen = append(hasExportExceptionsSeen, k)
if t.GetExportStrategy() == nil {
errs = append(errs, fmt.Errorf("store for type [%v] does not have an ExportStrategy", k))
}
} else {
// By default we expect Stores to not have additional export logic:
if t.GetExportStrategy() != nil {
errs = append(errs, fmt.Errorf("store for type [%v] has an unexpected ExportStrategy", k))
}
}
}
}
// Ensure that we saw all our expected exceptions:
for _, expKey := range exceptions.HasExportStrategy {
if !slice.ContainsString(hasExportExceptionsSeen, expKey, nil) {
errs = append(errs, fmt.Errorf("no generic store seen for expected ExportStrategy: %v", expKey))
} }
} }

View File

@ -388,7 +388,7 @@ func (r *crdHandler) serveResource(w http.ResponseWriter, req *http.Request, req
switch requestInfo.Verb { switch requestInfo.Verb {
case "get": case "get":
return handlers.GetResource(storage, storage, requestScope) return handlers.GetResource(storage, requestScope)
case "list": case "list":
forceWatch := false forceWatch := false
return handlers.ListResource(storage, storage, requestScope, forceWatch, r.minRequestTimeout) return handlers.ListResource(storage, storage, requestScope, forceWatch, r.minRequestTimeout)
@ -428,7 +428,7 @@ func (r *crdHandler) serveStatus(w http.ResponseWriter, req *http.Request, reque
switch requestInfo.Verb { switch requestInfo.Verb {
case "get": case "get":
return handlers.GetResource(storage, nil, requestScope) return handlers.GetResource(storage, requestScope)
case "update": case "update":
return handlers.UpdateResource(storage, requestScope, r.admission) return handlers.UpdateResource(storage, requestScope, r.admission)
case "patch": case "patch":
@ -448,7 +448,7 @@ func (r *crdHandler) serveScale(w http.ResponseWriter, req *http.Request, reques
switch requestInfo.Verb { switch requestInfo.Verb {
case "get": case "get":
return handlers.GetResource(storage, nil, requestScope) return handlers.GetResource(storage, requestScope)
case "update": case "update":
return handlers.UpdateResource(storage, requestScope, r.admission) return handlers.UpdateResource(storage, requestScope, r.admission)
case "patch": case "patch":
@ -698,7 +698,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
parameterScheme := runtime.NewScheme() parameterScheme := runtime.NewScheme()
parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name}, parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name},
&metav1.ListOptions{}, &metav1.ListOptions{},
&metav1.ExportOptions{},
&metav1.GetOptions{}, &metav1.GetOptions{},
&metav1.DeleteOptions{}, &metav1.DeleteOptions{},
) )

View File

@ -55,7 +55,6 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcd3testi
parameterScheme := runtime.NewScheme() parameterScheme := runtime.NewScheme()
parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"}, parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"},
&metav1.ListOptions{}, &metav1.ListOptions{},
&metav1.ExportOptions{},
&metav1.GetOptions{}, &metav1.GetOptions{},
&metav1.DeleteOptions{}, &metav1.DeleteOptions{},
) )

View File

@ -52,7 +52,6 @@ func addToGroupVersion(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion, scheme.AddKnownTypes(SchemeGroupVersion,
&ListOptions{}, &ListOptions{},
&metav1.GetOptions{}, &metav1.GetOptions{},
&metav1.ExportOptions{},
&metav1.DeleteOptions{}, &metav1.DeleteOptions{},
&metav1.CreateOptions{}, &metav1.CreateOptions{},
&metav1.UpdateOptions{}, &metav1.UpdateOptions{},

View File

@ -55,7 +55,6 @@ var ParameterCodec = runtime.NewParameterCodec(scheme)
var optionsTypes = []runtime.Object{ var optionsTypes = []runtime.Object{
&ListOptions{}, &ListOptions{},
&ExportOptions{},
&GetOptions{}, &GetOptions{},
&DeleteOptions{}, &DeleteOptions{},
&CreateOptions{}, &CreateOptions{},

View File

@ -142,10 +142,10 @@ func init() {
func addGrouplessTypes() { func addGrouplessTypes() {
scheme.AddKnownTypes(grouplessGroupVersion, scheme.AddKnownTypes(grouplessGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{}, &metav1.ExportOptions{}, &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{},
&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{})
scheme.AddKnownTypes(grouplessInternalGroupVersion, scheme.AddKnownTypes(grouplessInternalGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{})
utilruntime.Must(genericapitesting.RegisterConversions(scheme)) utilruntime.Must(genericapitesting.RegisterConversions(scheme))
@ -153,20 +153,20 @@ func addGrouplessTypes() {
func addTestTypes() { func addTestTypes() {
scheme.AddKnownTypes(testGroupVersion, scheme.AddKnownTypes(testGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
&genericapitesting.SimpleXGSubresource{}) &genericapitesting.SimpleXGSubresource{})
scheme.AddKnownTypes(testGroupVersion, &examplev1.Pod{}) scheme.AddKnownTypes(testGroupVersion, &examplev1.Pod{})
scheme.AddKnownTypes(testInternalGroupVersion, scheme.AddKnownTypes(testInternalGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
&genericapitesting.SimpleXGSubresource{}) &genericapitesting.SimpleXGSubresource{})
scheme.AddKnownTypes(testInternalGroupVersion, &example.Pod{}) scheme.AddKnownTypes(testInternalGroupVersion, &example.Pod{})
// Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their // Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their
// their corresponding internal versions, to verify that the desired group version object is // their corresponding internal versions, to verify that the desired group version object is
// served in the tests. // served in the tests.
scheme.AddKnownTypes(testGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{}) scheme.AddKnownTypes(testGroup2Version, &genericapitesting.SimpleXGSubresource{})
scheme.AddKnownTypes(testInternalGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{}) scheme.AddKnownTypes(testInternalGroup2Version, &genericapitesting.SimpleXGSubresource{})
metav1.AddToGroupVersion(scheme, testGroupVersion) metav1.AddToGroupVersion(scheme, testGroupVersion)
utilruntime.Must(genericapitesting.RegisterConversions(scheme)) utilruntime.Must(genericapitesting.RegisterConversions(scheme))
@ -174,7 +174,7 @@ func addTestTypes() {
func addNewTestTypes() { func addNewTestTypes() {
scheme.AddKnownTypes(newGroupVersion, scheme.AddKnownTypes(newGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, &genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
&examplev1.Pod{}, &examplev1.Pod{},
) )
@ -368,21 +368,6 @@ func (storage *SimpleRESTStorage) NamespaceScoped() bool {
return true return true
} }
func (storage *SimpleRESTStorage) Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error) {
obj, err := storage.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, err
}
s, ok := obj.(*genericapitesting.Simple)
if !ok {
return nil, fmt.Errorf("unexpected object")
}
// Set a marker to verify the method was called
s.Other = "exported"
return obj, storage.errors["export"]
}
func (storage *SimpleRESTStorage) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { func (storage *SimpleRESTStorage) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
return rest.NewDefaultTableConvertor(schema.GroupResource{Resource: "simple"}).ConvertToTable(ctx, obj, tableOptions) return rest.NewDefaultTableConvertor(schema.GroupResource{Resource: "simple"}).ConvertToTable(ctx, obj, tableOptions)
} }
@ -1556,55 +1541,6 @@ func TestMetadata(t *testing.T) {
} }
} }
func TestExport(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{
item: genericapitesting.Simple{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "1234",
CreationTimestamp: metav1.NewTime(time.Unix(10, 10)),
},
Other: "foo",
},
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
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/id?export=true")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
data, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
t.Fatalf("unexpected response: %#v\n%s\n", resp, string(data))
}
var itemOut genericapitesting.Simple
body, err := extractBody(resp, &itemOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if itemOut.Name != simpleStorage.item.Name {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
}
if itemOut.Other != "exported" {
t.Errorf("Expected: exported, saw: %s", itemOut.Other)
}
if utilfeature.DefaultFeatureGate.Enabled(features.RemoveSelfLink) == selfLinker.called {
t.Errorf("unexpected selfLinker.called: %v", selfLinker.called)
}
}
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
storage := map[string]rest.Storage{} storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{ simpleStorage := SimpleRESTStorage{

View File

@ -19,14 +19,15 @@ package handlers
import ( import (
"context" "context"
"fmt" "fmt"
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
"k8s.io/apimachinery/pkg/runtime/schema"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@ -81,22 +82,22 @@ func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc
} }
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object. // GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
func GetResource(r rest.Getter, e rest.Exporter, scope *RequestScope) http.HandlerFunc { func GetResource(r rest.Getter, scope *RequestScope) http.HandlerFunc {
return getResourceHandler(scope, return getResourceHandler(scope,
func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) { func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) {
// check for export // check for export
options := metav1.GetOptions{} options := metav1.GetOptions{}
if values := req.URL.Query(); len(values) > 0 { if values := req.URL.Query(); len(values) > 0 {
exports := metav1.ExportOptions{} if len(values["export"]) > 0 {
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &exports); err != nil { exportBool := true
err = errors.NewBadRequest(err.Error()) exportStrings := values["export"]
return nil, err err := runtime.Convert_Slice_string_To_bool(&exportStrings, &exportBool, nil)
} if err != nil {
if exports.Export { return nil, errors.NewBadRequest(fmt.Sprintf("the export parameter cannot be parsed: %v", err))
if e == nil { }
return nil, errors.NewBadRequest(fmt.Sprintf("export of %q is not supported", scope.Resource.Resource)) if exportBool {
return nil, errors.NewBadRequest("the export parameter, deprecated since v1.14, is no longer supported")
} }
return e.Export(ctx, name, exports)
} }
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil { if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil {
err = errors.NewBadRequest(err.Error()) err = errors.NewBadRequest(err.Error())

View File

@ -253,15 +253,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if !isMetadata { if !isMetadata {
storageMeta = defaultStorageMetadata{} storageMeta = defaultStorageMetadata{}
} }
exporter, isExporter := storage.(rest.Exporter)
if !isExporter {
exporter = nil
}
versionedExportOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ExportOptions"))
if err != nil {
return nil, nil, err
}
if isNamedCreater { if isNamedCreater {
isCreater = true isCreater = true
@ -684,7 +675,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if isGetterWithOptions { if isGetterWithOptions {
handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource) handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
} else { } else {
handler = restfulGetResource(getter, exporter, reqScope) handler = restfulGetResource(getter, reqScope)
} }
if needOverride { if needOverride {
@ -713,11 +704,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
return nil, nil, err return nil, nil, err
} }
} }
if isExporter {
if err := AddObjectParams(ws, route, versionedExportOptions); err != nil {
return nil, nil, err
}
}
addParams(route, action.Params) addParams(route, action.Params)
routes = append(routes, route) routes = append(routes, route)
case "LIST": // List all resources of a kind. case "LIST": // List all resources of a kind.
@ -1227,9 +1213,9 @@ func restfulPatchResource(r rest.Patcher, scope handlers.RequestScope, admit adm
} }
} }
func restfulGetResource(r rest.Getter, e rest.Exporter, scope handlers.RequestScope) restful.RouteFunction { func restfulGetResource(r rest.Getter, scope handlers.RequestScope) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) { return func(req *restful.Request, res *restful.Response) {
handlers.GetResource(r, e, &scope)(res.ResponseWriter, req.Request) handlers.GetResource(r, &scope)(res.ResponseWriter, req.Request)
} }
} }

View File

@ -19,7 +19,6 @@ package registry
import ( import (
"context" "context"
"fmt" "fmt"
"reflect"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -73,7 +72,6 @@ type GenericStore interface {
GetCreateStrategy() rest.RESTCreateStrategy GetCreateStrategy() rest.RESTCreateStrategy
GetUpdateStrategy() rest.RESTUpdateStrategy GetUpdateStrategy() rest.RESTUpdateStrategy
GetDeleteStrategy() rest.RESTDeleteStrategy GetDeleteStrategy() rest.RESTDeleteStrategy
GetExportStrategy() rest.RESTExportStrategy
} }
// Store implements k8s.io/apiserver/pkg/registry/rest.StandardStorage. It's // Store implements k8s.io/apiserver/pkg/registry/rest.StandardStorage. It's
@ -199,10 +197,6 @@ type Store struct {
// deletionTimestamp, and deletionGracePeriodSeconds checks. // deletionTimestamp, and deletionGracePeriodSeconds checks.
ShouldDeleteDuringUpdate func(ctx context.Context, key string, obj, existing runtime.Object) bool ShouldDeleteDuringUpdate func(ctx context.Context, key string, obj, existing runtime.Object) bool
// ExportStrategy implements resource-specific behavior during export,
// optional. Exported objects are not decorated.
ExportStrategy rest.RESTExportStrategy
// TableConvertor is an optional interface for transforming items or lists // TableConvertor is an optional interface for transforming items or lists
// of items into tabular output. If unset, the default will be used. // of items into tabular output. If unset, the default will be used.
TableConvertor rest.TableConvertor TableConvertor rest.TableConvertor
@ -223,7 +217,6 @@ type Store struct {
// Note: the rest.StandardStorage interface aggregates the common REST verbs // Note: the rest.StandardStorage interface aggregates the common REST verbs
var _ rest.StandardStorage = &Store{} var _ rest.StandardStorage = &Store{}
var _ rest.Exporter = &Store{}
var _ rest.TableConvertor = &Store{} var _ rest.TableConvertor = &Store{}
var _ GenericStore = &Store{} var _ GenericStore = &Store{}
@ -312,11 +305,6 @@ func (e *Store) GetDeleteStrategy() rest.RESTDeleteStrategy {
return e.DeleteStrategy return e.DeleteStrategy
} }
// GetExportStrategy implements GenericStore.
func (e *Store) GetExportStrategy() rest.RESTExportStrategy {
return e.ExportStrategy
}
// List returns a list of items matching labels and field according to the // List returns a list of items matching labels and field according to the
// store's PredicateFunc. // store's PredicateFunc.
func (e *Store) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { func (e *Store) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
@ -1267,44 +1255,6 @@ func (e *Store) calculateTTL(obj runtime.Object, defaultTTL int64, update bool)
return ttl, err return ttl, err
} }
// exportObjectMeta unsets the fields on the given object that should not be
// present when the object is exported.
func exportObjectMeta(accessor metav1.Object, exact bool) {
accessor.SetUID("")
if !exact {
accessor.SetNamespace("")
}
accessor.SetCreationTimestamp(metav1.Time{})
accessor.SetDeletionTimestamp(nil)
accessor.SetResourceVersion("")
accessor.SetSelfLink("")
if len(accessor.GetGenerateName()) > 0 && !exact {
accessor.SetName("")
}
}
// Export implements the rest.Exporter interface
func (e *Store) Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error) {
obj, err := e.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, err
}
if accessor, err := meta.Accessor(obj); err == nil {
exportObjectMeta(accessor, opts.Exact)
} else {
klog.V(4).Infof("Object of type %v does not have ObjectMeta: %v", reflect.TypeOf(obj), err)
}
if e.ExportStrategy != nil {
if err = e.ExportStrategy.Export(ctx, obj, opts.Exact); err != nil {
return nil, err
}
} else {
e.CreateStrategy.PrepareForCreate(ctx, obj)
}
return obj, nil
}
// CompleteWithOptions updates the store with the provided options and // CompleteWithOptions updates the store with the provided options and
// defaults common fields. // defaults common fields.
func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error { func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {

View File

@ -1162,104 +1162,6 @@ func TestStoreUpdateHooksInnerRetry(t *testing.T) {
} }
} }
// TODO: Add a test to check no-op update if we have object with ResourceVersion
// already stored in etcd. Currently there is no easy way to store object with
// ResourceVersion in etcd.
type testPodExport struct{}
func (t testPodExport) Export(ctx context.Context, obj runtime.Object, exact bool) error {
pod := obj.(*example.Pod)
if pod.Labels == nil {
pod.Labels = map[string]string{}
}
pod.Labels["exported"] = "true"
pod.Labels["exact"] = strconv.FormatBool(exact)
return nil
}
func TestStoreCustomExport(t *testing.T) {
podA := example.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "foo",
Labels: map[string]string{},
},
Spec: example.PodSpec{NodeName: "machine"},
}
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
registry.ExportStrategy = testPodExport{}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true
if !updateAndVerify(t, testContext, registry, &podA) {
t.Errorf("Unexpected error updating podA")
}
obj, err := registry.Export(testContext, podA.Name, metav1.ExportOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
exportedPod := obj.(*example.Pod)
if exportedPod.Labels["exported"] != "true" {
t.Errorf("expected: exported->true, found: %s", exportedPod.Labels["exported"])
}
if exportedPod.Labels["exact"] != "false" {
t.Errorf("expected: exact->false, found: %s", exportedPod.Labels["exact"])
}
if exportedPod.Labels["prepare_create"] != "true" {
t.Errorf("expected: prepare_create->true, found: %s", exportedPod.Labels["prepare_create"])
}
delete(exportedPod.Labels, "exported")
delete(exportedPod.Labels, "exact")
delete(exportedPod.Labels, "prepare_create")
exportObjectMeta(&podA.ObjectMeta, false)
podA.Spec = exportedPod.Spec
if !reflect.DeepEqual(&podA, exportedPod) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", &podA, exportedPod)
}
}
func TestStoreBasicExport(t *testing.T) {
podA := example.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "foo",
Labels: map[string]string{},
},
Spec: example.PodSpec{NodeName: "machine"},
Status: example.PodStatus{HostIP: "1.2.3.4"},
}
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true
if !updateAndVerify(t, testContext, registry, &podA) {
t.Errorf("Unexpected error updating podA")
}
obj, err := registry.Export(testContext, podA.Name, metav1.ExportOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
exportedPod := obj.(*example.Pod)
if exportedPod.Labels["prepare_create"] != "true" {
t.Errorf("expected: prepare_create->true, found: %s", exportedPod.Labels["prepare_create"])
}
delete(exportedPod.Labels, "prepare_create")
exportObjectMeta(&podA.ObjectMeta, false)
podA.Spec = exportedPod.Spec
if !reflect.DeepEqual(&podA, exportedPod) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", &podA, exportedPod)
}
}
func TestStoreGet(t *testing.T) { func TestStoreGet(t *testing.T) {
podA := &example.Pod{ podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo"},

View File

@ -25,7 +25,6 @@ go_library(
"create_update.go", "create_update.go",
"delete.go", "delete.go",
"doc.go", "doc.go",
"export.go",
"meta.go", "meta.go",
"rest.go", "rest.go",
"table.go", "table.go",

View File

@ -1,34 +0,0 @@
/*
Copyright 2015 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 rest
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
)
// RESTExportStrategy is the interface that defines how to export a Kubernetes
// object. An exported object is stripped of non-user-settable fields and
// optionally, the identifying information related to the object's identity in
// the cluster so that it can be loaded into a different namespace or entirely
// different cluster without conflict.
type RESTExportStrategy interface {
// Export strips fields that can not be set by the user. If 'exact' is false
// fields specific to the cluster are also stripped
Export(ctx context.Context, obj runtime.Object, exact bool) error
}

View File

@ -102,16 +102,6 @@ type Lister interface {
TableConvertor TableConvertor
} }
// Exporter is an object that knows how to strip a RESTful resource for export. A store should implement this interface
// if export is generally supported for that type. Errors can still be returned during the actual Export when certain
// instances of the type are not exportable.
type Exporter interface {
// Export an object. Fields that are not user specified (e.g. Status, ObjectMeta.ResourceVersion) are stripped out
// Returns the stripped object. If 'exact' is true, fields that are specific to the cluster (e.g. namespace) are
// retained, otherwise they are stripped also.
Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error)
}
// Getter is an object that can retrieve a named RESTful resource. // Getter is an object that can retrieve a named RESTful resource.
type Getter interface { type Getter interface {
// Get finds a resource in the storage by name and returns it. // Get finds a resource in the storage by name and returns it.

View File

@ -10,6 +10,7 @@ go_test(
size = "large", size = "large",
srcs = [ srcs = [
"apiserver_test.go", "apiserver_test.go",
"export_test.go",
"main_test.go", "main_test.go",
"max_json_patch_operations_test.go", "max_json_patch_operations_test.go",
"max_request_body_bytes_test.go", "max_request_body_bytes_test.go",

View File

@ -0,0 +1,57 @@
/*
Copyright 2020 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 apiserver
import (
"context"
"net/http"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Tests that the apiserver rejects the export param
func TestExportRejection(t *testing.T) {
_, clientSet, closeFn := setup(t)
defer closeFn()
_, err := clientSet.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "export-fail"},
}, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
defer func() {
clientSet.CoreV1().Namespaces().Delete(context.Background(), "export-fail", metav1.DeleteOptions{})
}()
result := clientSet.Discovery().RESTClient().Get().AbsPath("/api/v1/namespaces/export-fail").Param("export", "true").Do(context.Background())
statusCode := 0
result.StatusCode(&statusCode)
if statusCode != http.StatusBadRequest {
t.Errorf("expected %v, got %v", http.StatusBadRequest, statusCode)
}
result = clientSet.Discovery().RESTClient().Get().AbsPath("/api/v1/namespaces/export-fail").Param("export", "false").Do(context.Background())
statusCode = 0
result.StatusCode(&statusCode)
if statusCode != http.StatusOK {
t.Errorf("expected %v, got %v", http.StatusOK, statusCode)
}
}