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",
)
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
// 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.LabelSelector{}): true,
reflect.TypeOf(metav1.GetOptions{}): true,
reflect.TypeOf(metav1.ExportOptions{}): true,
reflect.TypeOf(metav1.ListOptions{}): true,
reflect.TypeOf(metav1.DeleteOptions{}): 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)
}
// Any new stores with export logic will need to be added here:
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)
strategyErrors := registrytest.ValidateStorageStrategies(apiGroupInfo.VersionedResourcesStorageMap["v1"])
for _, err := range strategyErrors {
t.Error(err)
}
@ -187,14 +176,8 @@ func TestCertificatesRestStorageStrategies(t *testing.T) {
t.Fatalf("unexpected error from REST storage: %v", err)
}
exceptions := registrytest.StrategyExceptions{
HasExportStrategy: []string{
"certificatesigningrequests",
},
}
strategyErrors := registrytest.ValidateStorageStrategies(
apiGroupInfo.VersionedResourcesStorageMap[certificatesapiv1beta1.SchemeGroupVersion.Version], exceptions)
apiGroupInfo.VersionedResourcesStorageMap[certificatesapiv1beta1.SchemeGroupVersion.Version])
for _, err := range strategyErrors {
t.Error(err)
}

View File

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

View File

@ -118,21 +118,6 @@ func (csrStrategy) AllowUnconditionalUpdate() bool {
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
type csrStatusStrategy struct {
csrStrategy

View File

@ -40,7 +40,6 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
CreateStrategy: limitrange.Strategy,
UpdateStrategy: limitrange.Strategy,
DeleteStrategy: limitrange.Strategy,
ExportStrategy: limitrange.Strategy,
// TODO: define table converter that exposes more than name/creation timestamp
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 {
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)
}
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
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{})

View File

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

View File

@ -141,22 +141,6 @@ func (nodeStrategy) AllowUnconditionalUpdate() bool {
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 {
nodeStrategy
}

View File

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

View File

@ -87,8 +87,3 @@ func (podTemplateStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.
func (podTemplateStrategy) AllowUnconditionalUpdate() bool {
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/apis/core: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/labels: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/apis/core: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,
UpdateStrategy: secret.Strategy,
DeleteStrategy: secret.Strategy,
ExportStrategy: secret.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}

View File

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -85,26 +84,6 @@ func (strategy) AllowUnconditionalUpdate() bool {
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.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
secret, ok := obj.(*api.Secret)

View File

@ -17,12 +17,8 @@ limitations under the License.
package secret
import (
"reflect"
"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"
api "k8s.io/kubernetes/pkg/apis/core"
@ -30,80 +26,6 @@ import (
_ "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) {
apitesting.TestSelectableFieldLabelConversionsOfKind(t,
"v1",

View File

@ -79,7 +79,6 @@ type ServiceStorage interface {
rest.CreaterUpdater
rest.GracefulDeleter
rest.Watcher
rest.Exporter
rest.StorageVersionProvider
}
@ -189,10 +188,6 @@ func (rs *REST) Watch(ctx context.Context, options *metainternalversion.ListOpti
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) {
service := obj.(*api.Service)

View File

@ -163,10 +163,6 @@ func (s *serviceStorage) ConvertToTable(ctx context.Context, object runtime.Obje
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 {
panic("not implemented")
}

View File

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

View File

@ -18,7 +18,6 @@ package service
import (
"context"
"fmt"
"net"
"reflect"
@ -37,7 +36,6 @@ import (
type Strategy interface {
rest.RESTCreateUpdateStrategy
rest.RESTExportStrategy
}
// svcStrategy implements behavior for Services
@ -139,30 +137,6 @@ func (svcStrategy) AllowUnconditionalUpdate() bool {
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
// are not enabled. The typical pattern is:
// if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) {

View File

@ -47,115 +47,6 @@ func newStrategy(cidr string, hasSecondary bool) (testStrategy Strategy, testSta
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) {
testStrategy, _ := newStrategy("10.0.0.0/16", false)
expect := errors.NewNotFound(api.Resource("foos"), "bar")

View File

@ -21,7 +21,6 @@ go_library(
deps = [
"//pkg/apis/core: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/apis/meta/internalversion: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
}
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/rest"
"k8s.io/kubernetes/pkg/util/slice"
)
// ValidateStorageStrategies ensures any instances of the generic registry.Store in the given storage map
// have expected strategies defined.
func ValidateStorageStrategies(storageMap map[string]rest.Storage, exceptions StrategyExceptions) []error {
func ValidateStorageStrategies(storageMap map[string]rest.Storage) []error {
errs := []error{}
// Used to ensure we saw all the expected exceptions:
hasExportExceptionsSeen := []string{}
for k, storage := range storageMap {
switch t := storage.(type) {
case registry.GenericStore:
@ -46,27 +42,6 @@ func ValidateStorageStrategies(storageMap map[string]rest.Storage, exceptions St
if t.GetDeleteStrategy() == nil {
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 {
case "get":
return handlers.GetResource(storage, storage, requestScope)
return handlers.GetResource(storage, requestScope)
case "list":
forceWatch := false
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 {
case "get":
return handlers.GetResource(storage, nil, requestScope)
return handlers.GetResource(storage, requestScope)
case "update":
return handlers.UpdateResource(storage, requestScope, r.admission)
case "patch":
@ -448,7 +448,7 @@ func (r *crdHandler) serveScale(w http.ResponseWriter, req *http.Request, reques
switch requestInfo.Verb {
case "get":
return handlers.GetResource(storage, nil, requestScope)
return handlers.GetResource(storage, requestScope)
case "update":
return handlers.UpdateResource(storage, requestScope, r.admission)
case "patch":
@ -698,7 +698,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
parameterScheme := runtime.NewScheme()
parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name},
&metav1.ListOptions{},
&metav1.ExportOptions{},
&metav1.GetOptions{},
&metav1.DeleteOptions{},
)

View File

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

View File

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

View File

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

View File

@ -142,10 +142,10 @@ func init() {
func addGrouplessTypes() {
scheme.AddKnownTypes(grouplessGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{}, &metav1.ExportOptions{},
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{},
&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{})
scheme.AddKnownTypes(grouplessInternalGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
&genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{})
utilruntime.Must(genericapitesting.RegisterConversions(scheme))
@ -153,20 +153,20 @@ func addGrouplessTypes() {
func addTestTypes() {
scheme.AddKnownTypes(testGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
&genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
&genericapitesting.SimpleXGSubresource{})
scheme.AddKnownTypes(testGroupVersion, &examplev1.Pod{})
scheme.AddKnownTypes(testInternalGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
&genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
&genericapitesting.SimpleXGSubresource{})
scheme.AddKnownTypes(testInternalGroupVersion, &example.Pod{})
// Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their
// their corresponding internal versions, to verify that the desired group version object is
// served in the tests.
scheme.AddKnownTypes(testGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{})
scheme.AddKnownTypes(testInternalGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{})
scheme.AddKnownTypes(testGroup2Version, &genericapitesting.SimpleXGSubresource{})
scheme.AddKnownTypes(testInternalGroup2Version, &genericapitesting.SimpleXGSubresource{})
metav1.AddToGroupVersion(scheme, testGroupVersion)
utilruntime.Must(genericapitesting.RegisterConversions(scheme))
@ -174,7 +174,7 @@ func addTestTypes() {
func addNewTestTypes() {
scheme.AddKnownTypes(newGroupVersion,
&genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{},
&genericapitesting.Simple{}, &genericapitesting.SimpleList{},
&metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{},
&examplev1.Pod{},
)
@ -368,21 +368,6 @@ func (storage *SimpleRESTStorage) NamespaceScoped() bool {
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) {
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) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{

View File

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

View File

@ -253,15 +253,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if !isMetadata {
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 {
isCreater = true
@ -684,7 +675,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if isGetterWithOptions {
handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
} else {
handler = restfulGetResource(getter, exporter, reqScope)
handler = restfulGetResource(getter, reqScope)
}
if needOverride {
@ -713,11 +704,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
return nil, nil, err
}
}
if isExporter {
if err := AddObjectParams(ws, route, versionedExportOptions); err != nil {
return nil, nil, err
}
}
addParams(route, action.Params)
routes = append(routes, route)
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) {
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 (
"context"
"fmt"
"reflect"
"strings"
"sync"
"time"
@ -73,7 +72,6 @@ type GenericStore interface {
GetCreateStrategy() rest.RESTCreateStrategy
GetUpdateStrategy() rest.RESTUpdateStrategy
GetDeleteStrategy() rest.RESTDeleteStrategy
GetExportStrategy() rest.RESTExportStrategy
}
// Store implements k8s.io/apiserver/pkg/registry/rest.StandardStorage. It's
@ -199,10 +197,6 @@ type Store struct {
// deletionTimestamp, and deletionGracePeriodSeconds checks.
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
// of items into tabular output. If unset, the default will be used.
TableConvertor rest.TableConvertor
@ -223,7 +217,6 @@ type Store struct {
// Note: the rest.StandardStorage interface aggregates the common REST verbs
var _ rest.StandardStorage = &Store{}
var _ rest.Exporter = &Store{}
var _ rest.TableConvertor = &Store{}
var _ GenericStore = &Store{}
@ -312,11 +305,6 @@ func (e *Store) GetDeleteStrategy() rest.RESTDeleteStrategy {
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
// store's PredicateFunc.
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
}
// 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
// defaults common fields.
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) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo"},

View File

@ -25,7 +25,6 @@ go_library(
"create_update.go",
"delete.go",
"doc.go",
"export.go",
"meta.go",
"rest.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
}
// 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.
type Getter interface {
// Get finds a resource in the storage by name and returns it.

View File

@ -10,6 +10,7 @@ go_test(
size = "large",
srcs = [
"apiserver_test.go",
"export_test.go",
"main_test.go",
"max_json_patch_operations_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)
}
}