Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
323
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go
generated
vendored
Normal file
323
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
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 customresource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
)
|
||||
|
||||
// CustomResourceStorage includes dummy storage for CustomResources, and their Status and Scale subresources.
|
||||
type CustomResourceStorage struct {
|
||||
CustomResource *REST
|
||||
Status *StatusREST
|
||||
Scale *ScaleREST
|
||||
}
|
||||
|
||||
func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor) CustomResourceStorage {
|
||||
customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter, categories, tableConvertor)
|
||||
customResourceRegistry := NewRegistry(customResourceREST)
|
||||
|
||||
s := CustomResourceStorage{
|
||||
CustomResource: customResourceREST,
|
||||
}
|
||||
|
||||
if strategy.status != nil {
|
||||
s.Status = customResourceStatusREST
|
||||
}
|
||||
|
||||
if scale := strategy.scale; scale != nil {
|
||||
var labelSelectorPath string
|
||||
if scale.LabelSelectorPath != nil {
|
||||
labelSelectorPath = *scale.LabelSelectorPath
|
||||
}
|
||||
|
||||
s.Scale = &ScaleREST{
|
||||
registry: customResourceRegistry,
|
||||
specReplicasPath: scale.SpecReplicasPath,
|
||||
statusReplicasPath: scale.StatusReplicasPath,
|
||||
labelSelectorPath: labelSelectorPath,
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// REST implements a RESTStorage for API services against etcd
|
||||
type REST struct {
|
||||
*genericregistry.Store
|
||||
categories []string
|
||||
}
|
||||
|
||||
// newREST returns a RESTStorage object that will work against API services.
|
||||
func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor) (*REST, *StatusREST) {
|
||||
store := &genericregistry.Store{
|
||||
NewFunc: func() runtime.Object { return &unstructured.Unstructured{} },
|
||||
NewListFunc: func() runtime.Object {
|
||||
// lists are never stored, only manufactured, so stomp in the right kind
|
||||
ret := &unstructured.UnstructuredList{}
|
||||
ret.SetGroupVersionKind(listKind)
|
||||
return ret
|
||||
},
|
||||
PredicateFunc: strategy.MatchCustomResourceDefinitionStorage,
|
||||
DefaultQualifiedResource: resource,
|
||||
|
||||
CreateStrategy: strategy,
|
||||
UpdateStrategy: strategy,
|
||||
DeleteStrategy: strategy,
|
||||
|
||||
TableConvertor: tableConvertor,
|
||||
}
|
||||
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: strategy.GetAttrs}
|
||||
if err := store.CompleteWithOptions(options); err != nil {
|
||||
panic(err) // TODO: Propagate error up
|
||||
}
|
||||
|
||||
statusStore := *store
|
||||
statusStore.UpdateStrategy = NewStatusStrategy(strategy)
|
||||
return &REST{store, categories}, &StatusREST{store: &statusStore}
|
||||
}
|
||||
|
||||
// Implement CategoriesProvider
|
||||
var _ rest.CategoriesProvider = &REST{}
|
||||
|
||||
// List returns a list of items matching labels and field according to the store's PredicateFunc.
|
||||
func (e *REST) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
l, err := e.Store.List(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Shallow copy ObjectMeta in returned list for each item. Native types have `Items []Item` fields and therefore
|
||||
// implicitly shallow copy ObjectMeta. The generic store sets the self-link for each item. So this is necessary
|
||||
// to avoid mutation of the objects from the cache.
|
||||
if ul, ok := l.(*unstructured.UnstructuredList); ok {
|
||||
for i := range ul.Items {
|
||||
shallowCopyObjectMeta(&ul.Items[i])
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
o, err := r.Store.Get(ctx, name, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u, ok := o.(*unstructured.Unstructured); ok {
|
||||
shallowCopyObjectMeta(u)
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func shallowCopyObjectMeta(u runtime.Unstructured) {
|
||||
obj := shallowMapDeepCopy(u.UnstructuredContent())
|
||||
if metadata, ok := obj["metadata"]; ok {
|
||||
if metadata, ok := metadata.(map[string]interface{}); ok {
|
||||
obj["metadata"] = shallowMapDeepCopy(metadata)
|
||||
u.SetUnstructuredContent(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shallowMapDeepCopy(in map[string]interface{}) map[string]interface{} {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make(map[string]interface{}, len(in))
|
||||
for k, v := range in {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
|
||||
func (r *REST) Categories() []string {
|
||||
return r.categories
|
||||
}
|
||||
|
||||
// StatusREST implements the REST endpoint for changing the status of a CustomResource
|
||||
type StatusREST struct {
|
||||
store *genericregistry.Store
|
||||
}
|
||||
|
||||
var _ = rest.Patcher(&StatusREST{})
|
||||
|
||||
func (r *StatusREST) New() runtime.Object {
|
||||
return &unstructured.Unstructured{}
|
||||
}
|
||||
|
||||
// Get retrieves the object from the storage. It is required to support Patch.
|
||||
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
return r.store.Get(ctx, name, options)
|
||||
}
|
||||
|
||||
// Update alters the status subset of an object.
|
||||
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool) (runtime.Object, bool, error) {
|
||||
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
|
||||
// subresources should never allow create on update.
|
||||
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false)
|
||||
}
|
||||
|
||||
type ScaleREST struct {
|
||||
registry Registry
|
||||
specReplicasPath string
|
||||
statusReplicasPath string
|
||||
labelSelectorPath string
|
||||
}
|
||||
|
||||
// ScaleREST implements Patcher
|
||||
var _ = rest.Patcher(&ScaleREST{})
|
||||
var _ = rest.GroupVersionKindProvider(&ScaleREST{})
|
||||
|
||||
func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
|
||||
return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
|
||||
}
|
||||
|
||||
// New creates a new Scale object
|
||||
func (r *ScaleREST) New() runtime.Object {
|
||||
return &autoscalingv1.Scale{}
|
||||
}
|
||||
|
||||
func (r *ScaleREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
cr, err := r.registry.GetCustomResource(ctx, name, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scaleObject, replicasFound, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !replicasFound {
|
||||
return nil, apierrors.NewInternalError(fmt.Errorf("the spec replicas field %q does not exist", r.specReplicasPath))
|
||||
}
|
||||
return scaleObject, err
|
||||
}
|
||||
|
||||
func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool) (runtime.Object, bool, error) {
|
||||
cr, err := r.registry.GetCustomResource(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
const invalidSpecReplicas = -2147483648 // smallest int32
|
||||
oldScale, replicasFound, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !replicasFound {
|
||||
oldScale.Spec.Replicas = invalidSpecReplicas // signal that this was not set before
|
||||
}
|
||||
|
||||
obj, err := objInfo.UpdatedObject(ctx, oldScale)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if obj == nil {
|
||||
return nil, false, apierrors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
|
||||
}
|
||||
|
||||
scale, ok := obj.(*autoscalingv1.Scale)
|
||||
if !ok {
|
||||
return nil, false, apierrors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
|
||||
}
|
||||
|
||||
if scale.Spec.Replicas == invalidSpecReplicas {
|
||||
return nil, false, apierrors.NewBadRequest(fmt.Sprintf("the spec replicas field %q cannot be empty", r.specReplicasPath))
|
||||
}
|
||||
|
||||
specReplicasPath := strings.TrimPrefix(r.specReplicasPath, ".") // ignore leading period
|
||||
if err = unstructured.SetNestedField(cr.Object, int64(scale.Spec.Replicas), strings.Split(specReplicasPath, ".")...); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
cr.SetResourceVersion(scale.ResourceVersion)
|
||||
|
||||
cr, err = r.registry.UpdateCustomResource(ctx, cr, createValidation, updateValidation)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
newScale, _, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath)
|
||||
if err != nil {
|
||||
return nil, false, apierrors.NewBadRequest(err.Error())
|
||||
}
|
||||
return newScale, false, err
|
||||
}
|
||||
|
||||
// scaleFromCustomResource returns a scale subresource for a customresource and a bool signalling wether
|
||||
// the specReplicas value was found.
|
||||
func scaleFromCustomResource(cr *unstructured.Unstructured, specReplicasPath, statusReplicasPath, labelSelectorPath string) (*autoscalingv1.Scale, bool, error) {
|
||||
specReplicasPath = strings.TrimPrefix(specReplicasPath, ".") // ignore leading period
|
||||
specReplicas, foundSpecReplicas, err := unstructured.NestedInt64(cr.UnstructuredContent(), strings.Split(specReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if !foundSpecReplicas {
|
||||
specReplicas = 0
|
||||
}
|
||||
|
||||
statusReplicasPath = strings.TrimPrefix(statusReplicasPath, ".") // ignore leading period
|
||||
statusReplicas, found, err := unstructured.NestedInt64(cr.UnstructuredContent(), strings.Split(statusReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if !found {
|
||||
statusReplicas = 0
|
||||
}
|
||||
|
||||
var labelSelector string
|
||||
if len(labelSelectorPath) > 0 {
|
||||
labelSelectorPath = strings.TrimPrefix(labelSelectorPath, ".") // ignore leading period
|
||||
labelSelector, found, err = unstructured.NestedString(cr.UnstructuredContent(), strings.Split(labelSelectorPath, ".")...)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
scale := &autoscalingv1.Scale{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cr.GetName(),
|
||||
Namespace: cr.GetNamespace(),
|
||||
UID: cr.GetUID(),
|
||||
ResourceVersion: cr.GetResourceVersion(),
|
||||
CreationTimestamp: cr.GetCreationTimestamp(),
|
||||
},
|
||||
Spec: autoscalingv1.ScaleSpec{
|
||||
Replicas: int32(specReplicas),
|
||||
},
|
||||
Status: autoscalingv1.ScaleStatus{
|
||||
Replicas: int32(statusReplicas),
|
||||
Selector: labelSelector,
|
||||
},
|
||||
}
|
||||
|
||||
return scale, foundSpecReplicas, nil
|
||||
}
|
575
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go
generated
vendored
Normal file
575
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go
generated
vendored
Normal file
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
Copyright 2018 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 customresource_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
registrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver"
|
||||
"k8s.io/apiextensions-apiserver/pkg/crdserverscheme"
|
||||
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
|
||||
"k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
|
||||
)
|
||||
|
||||
func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtesting.EtcdTestServer) {
|
||||
server, etcdStorage := etcdtesting.NewUnsecuredEtcd3TestClientServer(t)
|
||||
etcdStorage.Codec = unstructuredJsonCodec{}
|
||||
restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "noxus"}
|
||||
|
||||
parameterScheme := runtime.NewScheme()
|
||||
parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"},
|
||||
&metav1.ListOptions{},
|
||||
&metav1.ExportOptions{},
|
||||
&metav1.GetOptions{},
|
||||
&metav1.DeleteOptions{},
|
||||
)
|
||||
|
||||
typer := apiserver.UnstructuredObjectTyper{
|
||||
Delegate: parameterScheme,
|
||||
UnstructuredTyper: crdserverscheme.NewUnstructuredObjectTyper(),
|
||||
}
|
||||
|
||||
kind := schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "Noxu"}
|
||||
|
||||
labelSelectorPath := ".status.labelSelector"
|
||||
scale := &apiextensions.CustomResourceSubresourceScale{
|
||||
SpecReplicasPath: ".spec.replicas",
|
||||
StatusReplicasPath: ".status.replicas",
|
||||
LabelSelectorPath: &labelSelectorPath,
|
||||
}
|
||||
|
||||
status := &apiextensions.CustomResourceSubresourceStatus{}
|
||||
|
||||
headers := []apiextensions.CustomResourceColumnDefinition{
|
||||
{Name: "Age", Type: "date", JSONPath: ".metadata.creationTimestamp"},
|
||||
{Name: "Replicas", Type: "integer", JSONPath: ".spec.replicas"},
|
||||
{Name: "Missing", Type: "string", JSONPath: ".spec.missing"},
|
||||
{Name: "Invalid", Type: "integer", JSONPath: ".spec.string"},
|
||||
{Name: "String", Type: "string", JSONPath: ".spec.string"},
|
||||
{Name: "StringFloat64", Type: "string", JSONPath: ".spec.float64"},
|
||||
{Name: "StringInt64", Type: "string", JSONPath: ".spec.replicas"},
|
||||
{Name: "StringBool", Type: "string", JSONPath: ".spec.bool"},
|
||||
{Name: "Float64", Type: "number", JSONPath: ".spec.float64"},
|
||||
{Name: "Bool", Type: "boolean", JSONPath: ".spec.bool"},
|
||||
}
|
||||
table, _ := tableconvertor.New(headers)
|
||||
|
||||
storage := customresource.NewStorage(
|
||||
schema.GroupResource{Group: "mygroup.example.com", Resource: "noxus"},
|
||||
schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"},
|
||||
customresource.NewStrategy(
|
||||
typer,
|
||||
true,
|
||||
kind,
|
||||
nil,
|
||||
nil,
|
||||
status,
|
||||
scale,
|
||||
),
|
||||
restOptions,
|
||||
[]string{"all"},
|
||||
table,
|
||||
)
|
||||
|
||||
return storage, server
|
||||
}
|
||||
|
||||
// createCustomResource is a helper function that returns a CustomResource with the updated resource version.
|
||||
func createCustomResource(storage *customresource.REST, cr unstructured.Unstructured, t *testing.T) (unstructured.Unstructured, error) {
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), cr.GetNamespace())
|
||||
obj, err := storage.Create(ctx, &cr, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create CustomResource, %v", err)
|
||||
}
|
||||
newCR := obj.(*unstructured.Unstructured)
|
||||
return *newCR, nil
|
||||
}
|
||||
|
||||
func validNewCustomResource() *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "mygroup.example.com/v1beta1",
|
||||
"kind": "Noxu",
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": "default",
|
||||
"name": "foo",
|
||||
"creationTimestamp": time.Now().Add(-time.Hour*12 - 30*time.Minute).UTC().Format(time.RFC3339),
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(7),
|
||||
"string": "string",
|
||||
"float64": float64(3.1415926),
|
||||
"bool": true,
|
||||
"stringList": []interface{}{"foo", "bar"},
|
||||
"mixedList": []interface{}{"foo", int64(42)},
|
||||
"nonPrimitiveList": []interface{}{"foo", []interface{}{int64(1), int64(2)}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var validCustomResource = *validNewCustomResource()
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
test := registrytest.New(t, storage.CustomResource.Store)
|
||||
cr := validNewCustomResource()
|
||||
cr.SetNamespace("")
|
||||
test.TestCreate(
|
||||
cr,
|
||||
)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
test := registrytest.New(t, storage.CustomResource.Store)
|
||||
test.TestGet(validNewCustomResource())
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
test := registrytest.New(t, storage.CustomResource.Store)
|
||||
test.TestList(validNewCustomResource())
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
test := registrytest.New(t, storage.CustomResource.Store)
|
||||
test.TestDelete(validNewCustomResource())
|
||||
}
|
||||
|
||||
func TestGenerationNumber(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
modifiedRno := *validNewCustomResource()
|
||||
modifiedRno.SetGeneration(10)
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
cr, err := createCustomResource(storage.CustomResource, modifiedRno, t)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
etcdCR, err := storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
storedCR, _ := etcdCR.(*unstructured.Unstructured)
|
||||
|
||||
// Generation initialization
|
||||
if storedCR.GetGeneration() != 1 {
|
||||
t.Fatalf("Unexpected generation number %v", storedCR.GetGeneration())
|
||||
}
|
||||
|
||||
// Updates to spec should increment the generation number
|
||||
setSpecReplicas(storedCR, getSpecReplicas(storedCR)+1)
|
||||
if _, _, err := storage.CustomResource.Update(ctx, storedCR.GetName(), rest.DefaultUpdatedObjectInfo(storedCR), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
etcdCR, err = storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
storedCR, _ = etcdCR.(*unstructured.Unstructured)
|
||||
if storedCR.GetGeneration() != 2 {
|
||||
t.Fatalf("Unexpected generation, spec: %v", storedCR.GetGeneration())
|
||||
}
|
||||
|
||||
// Updates to status should not increment the generation number
|
||||
setStatusReplicas(storedCR, getStatusReplicas(storedCR)+1)
|
||||
if _, _, err := storage.CustomResource.Update(ctx, storedCR.GetName(), rest.DefaultUpdatedObjectInfo(storedCR), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
etcdCR, err = storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
storedCR, _ = etcdCR.(*unstructured.Unstructured)
|
||||
if storedCR.GetGeneration() != 2 {
|
||||
t.Fatalf("Unexpected generation, spec: %v", storedCR.GetGeneration())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCategories(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
|
||||
expected := []string{"all"}
|
||||
actual := storage.CustomResource.Categories()
|
||||
ok := reflect.DeepEqual(actual, expected)
|
||||
if !ok {
|
||||
t.Errorf("categories are not equal. expected = %v actual = %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumns(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
|
||||
validCustomResource := validNewCustomResource()
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
gottenList, err := storage.CustomResource.List(ctx, &metainternal.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
tbl, err := storage.CustomResource.ConvertToTable(ctx, gottenList, &metav1beta1.TableOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedColumns := []struct {
|
||||
Name, Type string
|
||||
}{
|
||||
{"Name", "string"},
|
||||
{"Age", "date"},
|
||||
{"Replicas", "integer"},
|
||||
{"Missing", "string"},
|
||||
{"Invalid", "integer"},
|
||||
{"String", "string"},
|
||||
{"StringFloat64", "string"},
|
||||
{"StringInt64", "string"},
|
||||
{"StringBool", "string"},
|
||||
{"Float64", "number"},
|
||||
{"Bool", "boolean"},
|
||||
}
|
||||
if len(tbl.ColumnDefinitions) != len(expectedColumns) {
|
||||
t.Fatalf("got %d columns, expected %d. Got: %+v", len(tbl.ColumnDefinitions), len(expectedColumns), tbl.ColumnDefinitions)
|
||||
}
|
||||
for i, d := range tbl.ColumnDefinitions {
|
||||
if d.Name != expectedColumns[i].Name {
|
||||
t.Errorf("got column %d name %q, expected %q", i, d.Name, expectedColumns[i].Name)
|
||||
}
|
||||
if d.Type != expectedColumns[i].Type {
|
||||
t.Errorf("got column %d type %q, expected %q", i, d.Type, expectedColumns[i].Type)
|
||||
}
|
||||
}
|
||||
|
||||
expectedRows := [][]interface{}{
|
||||
{
|
||||
"foo",
|
||||
"12h",
|
||||
int64(7),
|
||||
nil,
|
||||
nil,
|
||||
"string",
|
||||
"3.1415926",
|
||||
"7",
|
||||
"true",
|
||||
float64(3.1415926),
|
||||
true,
|
||||
},
|
||||
}
|
||||
for i, r := range tbl.Rows {
|
||||
if !reflect.DeepEqual(r.Cells, expectedRows[i]) {
|
||||
t.Errorf("got row %d with cells %#v, expected %#v", i, r.Cells, expectedRows[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusUpdate(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
|
||||
validCustomResource := validNewCustomResource()
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
gottenObj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
update := gottenObj.(*unstructured.Unstructured)
|
||||
updateContent := update.Object
|
||||
updateContent["status"] = map[string]interface{}{
|
||||
"replicas": int64(7),
|
||||
}
|
||||
|
||||
if _, _, err := storage.Status.Update(ctx, update.GetName(), rest.DefaultUpdatedObjectInfo(update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
obj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
cr, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
t.Fatal("unexpected error: custom resource should be of type Unstructured")
|
||||
}
|
||||
content := cr.UnstructuredContent()
|
||||
|
||||
spec := content["spec"].(map[string]interface{})
|
||||
status := content["status"].(map[string]interface{})
|
||||
|
||||
if spec["replicas"].(int64) != 7 {
|
||||
t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", spec["replicas"].(int64))
|
||||
}
|
||||
if status["replicas"].(int64) != 7 {
|
||||
t.Errorf("we expected .status.replicas to be updated to %d but it was %v", 7, status["replicas"].(int64))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaleGet(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
|
||||
name := "foo"
|
||||
|
||||
var cr unstructured.Unstructured
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
|
||||
}
|
||||
|
||||
want := &autoscalingv1.Scale{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cr.GetName(),
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
UID: cr.GetUID(),
|
||||
ResourceVersion: cr.GetResourceVersion(),
|
||||
CreationTimestamp: cr.GetCreationTimestamp(),
|
||||
},
|
||||
Spec: autoscalingv1.ScaleSpec{
|
||||
Replicas: int32(7),
|
||||
},
|
||||
}
|
||||
|
||||
obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error fetching scale for %s: %v", name, err)
|
||||
}
|
||||
|
||||
got := obj.(*autoscalingv1.Scale)
|
||||
if !apiequality.Semantic.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected scale: %s", diff.ObjectDiff(got, want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaleGetWithoutSpecReplicas(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
|
||||
name := "foo"
|
||||
|
||||
var cr unstructured.Unstructured
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
withoutSpecReplicas := validCustomResource.DeepCopy()
|
||||
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
|
||||
}
|
||||
|
||||
_, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err == nil {
|
||||
t.Fatalf("error expected for %s", name)
|
||||
}
|
||||
if expected := `the spec replicas field ".spec.replicas" does not exist`; !strings.Contains(err.Error(), expected) {
|
||||
t.Fatalf("expected error string %q, got: %v", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaleUpdate(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
|
||||
name := "foo"
|
||||
|
||||
var cr unstructured.Unstructured
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
|
||||
}
|
||||
|
||||
obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error fetching scale for %s: %v", name, err)
|
||||
}
|
||||
scale, ok := obj.(*autoscalingv1.Scale)
|
||||
if !ok {
|
||||
t.Fatalf("%v is not of the type autoscalingv1.Scale", scale)
|
||||
}
|
||||
|
||||
replicas := 12
|
||||
update := autoscalingv1.Scale{
|
||||
ObjectMeta: scale.ObjectMeta,
|
||||
Spec: autoscalingv1.ScaleSpec{
|
||||
Replicas: int32(replicas),
|
||||
},
|
||||
}
|
||||
|
||||
if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false); err != nil {
|
||||
t.Fatalf("error updating scale %v: %v", update, err)
|
||||
}
|
||||
|
||||
obj, err = storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error fetching scale for %s: %v", name, err)
|
||||
}
|
||||
scale = obj.(*autoscalingv1.Scale)
|
||||
if scale.Spec.Replicas != int32(replicas) {
|
||||
t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
|
||||
}
|
||||
|
||||
update.ResourceVersion = scale.ResourceVersion
|
||||
update.Spec.Replicas = 15
|
||||
|
||||
if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false); err != nil && !errors.IsConflict(err) {
|
||||
t.Fatalf("unexpected error, expecting an update conflict but got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaleUpdateWithoutSpecReplicas(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.CustomResource.Store.DestroyFunc()
|
||||
|
||||
name := "foo"
|
||||
|
||||
var cr unstructured.Unstructured
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
withoutSpecReplicas := validCustomResource.DeepCopy()
|
||||
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
|
||||
}
|
||||
|
||||
replicas := 12
|
||||
update := autoscalingv1.Scale{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
ResourceVersion: cr.GetResourceVersion(),
|
||||
},
|
||||
Spec: autoscalingv1.ScaleSpec{
|
||||
Replicas: int32(replicas),
|
||||
},
|
||||
}
|
||||
|
||||
if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false); err != nil {
|
||||
t.Fatalf("error updating scale %v: %v", update, err)
|
||||
}
|
||||
|
||||
obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error fetching scale for %s: %v", name, err)
|
||||
}
|
||||
scale := obj.(*autoscalingv1.Scale)
|
||||
if scale.Spec.Replicas != int32(replicas) {
|
||||
t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
|
||||
}
|
||||
}
|
||||
|
||||
type unstructuredJsonCodec struct{}
|
||||
|
||||
func (c unstructuredJsonCodec) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
|
||||
obj := into.(*unstructured.Unstructured)
|
||||
err := obj.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
gvk := obj.GroupVersionKind()
|
||||
return obj, &gvk, nil
|
||||
}
|
||||
|
||||
func (c unstructuredJsonCodec) Encode(obj runtime.Object, w io.Writer) error {
|
||||
u := obj.(*unstructured.Unstructured)
|
||||
bs, err := u.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Write(bs)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSpecReplicas(u *unstructured.Unstructured, replicas int64) {
|
||||
setNestedField(u, replicas, "spec", "replicas")
|
||||
}
|
||||
|
||||
func getSpecReplicas(u *unstructured.Unstructured) int64 {
|
||||
val, found, err := unstructured.NestedInt64(u.Object, "spec", "replicas")
|
||||
if !found || err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func setStatusReplicas(u *unstructured.Unstructured, replicas int64) {
|
||||
setNestedField(u, replicas, "status", "replicas")
|
||||
}
|
||||
|
||||
func getStatusReplicas(u *unstructured.Unstructured) int64 {
|
||||
val, found, err := unstructured.NestedInt64(u.Object, "status", "replicas")
|
||||
if !found || err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func setNestedField(u *unstructured.Unstructured, value interface{}, fields ...string) {
|
||||
if u.Object == nil {
|
||||
u.Object = make(map[string]interface{})
|
||||
}
|
||||
unstructured.SetNestedField(u.Object, value, fields...)
|
||||
}
|
104
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/registry.go
generated
vendored
Normal file
104
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/registry.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
Copyright 2018 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 customresource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
)
|
||||
|
||||
// Registry is an interface for things that know how to store CustomResources.
|
||||
type Registry interface {
|
||||
ListCustomResources(ctx context.Context, options *metainternalversion.ListOptions) (*unstructured.UnstructuredList, error)
|
||||
WatchCustomResources(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error)
|
||||
GetCustomResource(ctx context.Context, customResourceID string, options *metav1.GetOptions) (*unstructured.Unstructured, error)
|
||||
CreateCustomResource(ctx context.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc) (*unstructured.Unstructured, error)
|
||||
UpdateCustomResource(ctx context.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*unstructured.Unstructured, error)
|
||||
DeleteCustomResource(ctx context.Context, customResourceID string) error
|
||||
}
|
||||
|
||||
// storage puts strong typing around storage calls
|
||||
type storage struct {
|
||||
rest.StandardStorage
|
||||
}
|
||||
|
||||
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
|
||||
// types will panic.
|
||||
func NewRegistry(s rest.StandardStorage) Registry {
|
||||
return &storage{s}
|
||||
}
|
||||
|
||||
func (s *storage) ListCustomResources(ctx context.Context, options *metainternalversion.ListOptions) (*unstructured.UnstructuredList, error) {
|
||||
if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() {
|
||||
return nil, fmt.Errorf("field selector not supported yet")
|
||||
}
|
||||
obj, err := s.List(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*unstructured.UnstructuredList), err
|
||||
}
|
||||
|
||||
func (s *storage) WatchCustomResources(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
|
||||
return s.Watch(ctx, options)
|
||||
}
|
||||
|
||||
func (s *storage) GetCustomResource(ctx context.Context, customResourceID string, options *metav1.GetOptions) (*unstructured.Unstructured, error) {
|
||||
obj, err := s.Get(ctx, customResourceID, options)
|
||||
customResource, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("custom resource must be of type Unstructured")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
apiVersion := customResource.GetAPIVersion()
|
||||
groupVersion := strings.Split(apiVersion, "/")
|
||||
group := groupVersion[0]
|
||||
return nil, errors.NewNotFound(schema.GroupResource{Group: group, Resource: "scale"}, customResourceID)
|
||||
}
|
||||
return customResource, nil
|
||||
}
|
||||
|
||||
func (s *storage) CreateCustomResource(ctx context.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc) (*unstructured.Unstructured, error) {
|
||||
obj, err := s.Create(ctx, customResource, rest.ValidateAllObjectFunc, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*unstructured.Unstructured), nil
|
||||
}
|
||||
|
||||
func (s *storage) UpdateCustomResource(ctx context.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*unstructured.Unstructured, error) {
|
||||
obj, _, err := s.Update(ctx, customResource.GetName(), rest.DefaultUpdatedObjectInfo(customResource), createValidation, updateValidation, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*unstructured.Unstructured), nil
|
||||
}
|
||||
|
||||
func (s *storage) DeleteCustomResource(ctx context.Context, customResourceID string) error {
|
||||
_, _, err := s.Delete(ctx, customResourceID, nil)
|
||||
return err
|
||||
}
|
59
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go
generated
vendored
Normal file
59
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2018 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 customresource
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
type statusStrategy struct {
|
||||
customResourceStrategy
|
||||
}
|
||||
|
||||
func NewStatusStrategy(strategy customResourceStrategy) statusStrategy {
|
||||
return statusStrategy{strategy}
|
||||
}
|
||||
|
||||
func (a statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||
// update is only allowed to set status
|
||||
newCustomResourceObject := obj.(*unstructured.Unstructured)
|
||||
newCustomResource := newCustomResourceObject.UnstructuredContent()
|
||||
status, ok := newCustomResource["status"]
|
||||
|
||||
// copy old object into new object
|
||||
oldCustomResourceObject := old.(*unstructured.Unstructured)
|
||||
// overridding the resourceVersion in metadata is safe here, we have already checked that
|
||||
// new object and old object have the same resourceVersion.
|
||||
*newCustomResourceObject = *oldCustomResourceObject.DeepCopy()
|
||||
|
||||
// set status
|
||||
newCustomResource = newCustomResourceObject.UnstructuredContent()
|
||||
if ok {
|
||||
newCustomResource["status"] = status
|
||||
} else {
|
||||
delete(newCustomResource, "status")
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateUpdate is the default update validation for an end user updating status.
|
||||
func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, obj, old, a.scale)
|
||||
}
|
138
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy_test.go
generated
vendored
Normal file
138
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy_test.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright 2018 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 customresource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestPrepareForUpdate(t *testing.T) {
|
||||
strategy := statusStrategy{}
|
||||
tcs := []struct {
|
||||
old *unstructured.Unstructured
|
||||
obj *unstructured.Unstructured
|
||||
expected *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
// changes to spec are ignored
|
||||
old: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
},
|
||||
},
|
||||
obj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "new",
|
||||
},
|
||||
},
|
||||
expected: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// changes to other places are also ignored
|
||||
old: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
},
|
||||
},
|
||||
obj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"new": "new",
|
||||
},
|
||||
},
|
||||
expected: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// delete status
|
||||
old: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
"status": "old",
|
||||
},
|
||||
},
|
||||
obj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
},
|
||||
},
|
||||
expected: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// update status
|
||||
old: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
"status": "old",
|
||||
},
|
||||
},
|
||||
obj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"status": "new",
|
||||
},
|
||||
},
|
||||
expected: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
"status": "new",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// update status and other parts
|
||||
old: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
"status": "old",
|
||||
},
|
||||
},
|
||||
obj: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "new",
|
||||
"new": "new",
|
||||
"status": "new",
|
||||
},
|
||||
},
|
||||
expected: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": "old",
|
||||
"status": "new",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for index, tc := range tcs {
|
||||
strategy.PrepareForUpdate(context.TODO(), tc.obj, tc.old)
|
||||
if !reflect.DeepEqual(tc.obj, tc.expected) {
|
||||
t.Errorf("test %d failed: expected: %v, got %v", index, tc.expected, tc.obj)
|
||||
}
|
||||
}
|
||||
}
|
185
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go
generated
vendored
Normal file
185
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
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 customresource
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/validate"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||
)
|
||||
|
||||
// customResourceStrategy implements behavior for CustomResources.
|
||||
type customResourceStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
names.NameGenerator
|
||||
|
||||
namespaceScoped bool
|
||||
validator customResourceValidator
|
||||
status *apiextensions.CustomResourceSubresourceStatus
|
||||
scale *apiextensions.CustomResourceSubresourceScale
|
||||
}
|
||||
|
||||
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, schemaValidator, statusSchemaValidator *validate.SchemaValidator, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy {
|
||||
return customResourceStrategy{
|
||||
ObjectTyper: typer,
|
||||
NameGenerator: names.SimpleNameGenerator,
|
||||
namespaceScoped: namespaceScoped,
|
||||
status: status,
|
||||
scale: scale,
|
||||
validator: customResourceValidator{
|
||||
namespaceScoped: namespaceScoped,
|
||||
kind: kind,
|
||||
schemaValidator: schemaValidator,
|
||||
statusSchemaValidator: statusSchemaValidator,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a customResourceStrategy) NamespaceScoped() bool {
|
||||
return a.namespaceScoped
|
||||
}
|
||||
|
||||
// PrepareForCreate clears the status of a CustomResource before creation.
|
||||
func (a customResourceStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && a.status != nil {
|
||||
customResourceObject := obj.(*unstructured.Unstructured)
|
||||
customResource := customResourceObject.UnstructuredContent()
|
||||
|
||||
// create cannot set status
|
||||
if _, ok := customResource["status"]; ok {
|
||||
delete(customResource, "status")
|
||||
}
|
||||
}
|
||||
|
||||
accessor, _ := meta.Accessor(obj)
|
||||
accessor.SetGeneration(1)
|
||||
}
|
||||
|
||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||
func (a customResourceStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) || a.status == nil {
|
||||
return
|
||||
}
|
||||
|
||||
newCustomResourceObject := obj.(*unstructured.Unstructured)
|
||||
oldCustomResourceObject := old.(*unstructured.Unstructured)
|
||||
|
||||
newCustomResource := newCustomResourceObject.UnstructuredContent()
|
||||
oldCustomResource := oldCustomResourceObject.UnstructuredContent()
|
||||
|
||||
// update is not allowed to set status
|
||||
_, ok1 := newCustomResource["status"]
|
||||
_, ok2 := oldCustomResource["status"]
|
||||
switch {
|
||||
case ok2:
|
||||
newCustomResource["status"] = oldCustomResource["status"]
|
||||
case ok1:
|
||||
delete(newCustomResource, "status")
|
||||
}
|
||||
|
||||
// Any changes to the spec increment the generation number, any changes to the
|
||||
// status should reflect the generation number of the corresponding object. We push
|
||||
// the burden of managing the status onto the clients because we can't (in general)
|
||||
// know here what version of spec the writer of the status has seen. It may seem like
|
||||
// we can at first -- since obj contains spec -- but in the future we will probably make
|
||||
// status its own object, and even if we don't, writes may be the result of a
|
||||
// read-update-write loop, so the contents of spec may not actually be the spec that
|
||||
// the CustomResource has *seen*.
|
||||
newSpec, ok1 := newCustomResource["spec"]
|
||||
oldSpec, ok2 := oldCustomResource["spec"]
|
||||
|
||||
// spec is changed, created or deleted
|
||||
if (ok1 && ok2 && !apiequality.Semantic.DeepEqual(oldSpec, newSpec)) || (ok1 && !ok2) || (!ok1 && ok2) {
|
||||
oldAccessor, _ := meta.Accessor(oldCustomResourceObject)
|
||||
newAccessor, _ := meta.Accessor(newCustomResourceObject)
|
||||
newAccessor.SetGeneration(oldAccessor.GetGeneration() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates a new CustomResource.
|
||||
func (a customResourceStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
return a.validator.Validate(ctx, obj, a.scale)
|
||||
}
|
||||
|
||||
// Canonicalize normalizes the object after validation.
|
||||
func (customResourceStrategy) Canonicalize(obj runtime.Object) {
|
||||
}
|
||||
|
||||
// AllowCreateOnUpdate is false for CustomResources; this means a POST is
|
||||
// needed to create one.
|
||||
func (customResourceStrategy) AllowCreateOnUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AllowUnconditionalUpdate is the default update policy for CustomResource objects.
|
||||
func (customResourceStrategy) AllowUnconditionalUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateUpdate is the default update validation for an end user updating status.
|
||||
func (a customResourceStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return a.validator.ValidateUpdate(ctx, obj, old, a.scale)
|
||||
}
|
||||
|
||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
||||
func (a customResourceStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
return labels.Set(accessor.GetLabels()), objectMetaFieldsSet(accessor, a.namespaceScoped), accessor.GetInitializers() != nil, nil
|
||||
}
|
||||
|
||||
// objectMetaFieldsSet returns a fields that represent the ObjectMeta.
|
||||
func objectMetaFieldsSet(objectMeta metav1.Object, namespaceScoped bool) fields.Set {
|
||||
if namespaceScoped {
|
||||
return fields.Set{
|
||||
"metadata.name": objectMeta.GetName(),
|
||||
"metadata.namespace": objectMeta.GetNamespace(),
|
||||
}
|
||||
}
|
||||
return fields.Set{
|
||||
"metadata.name": objectMeta.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
// MatchCustomResourceDefinitionStorage is the filter used by the generic etcd backend to route
|
||||
// watch events from etcd to clients of the apiserver only interested in specific
|
||||
// labels/fields.
|
||||
func (a customResourceStrategy) MatchCustomResourceDefinitionStorage(label labels.Selector, field fields.Selector) apiserverstorage.SelectionPredicate {
|
||||
return apiserverstorage.SelectionPredicate{
|
||||
Label: label,
|
||||
Field: field,
|
||||
GetAttrs: a.GetAttrs,
|
||||
}
|
||||
}
|
172
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go
generated
vendored
Normal file
172
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2018 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 tableconvertor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metatable "k8s.io/apimachinery/pkg/api/meta/table"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
)
|
||||
|
||||
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
||||
|
||||
// New creates a new table convertor for the provided CRD column definition. If the printer definition cannot be parsed,
|
||||
// error will be returned along with a default table convertor.
|
||||
func New(crdColumns []apiextensions.CustomResourceColumnDefinition) (rest.TableConvertor, error) {
|
||||
headers := []metav1beta1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
|
||||
}
|
||||
c := &convertor{
|
||||
headers: headers,
|
||||
}
|
||||
|
||||
for _, col := range crdColumns {
|
||||
path := jsonpath.New(col.Name)
|
||||
if err := path.Parse(fmt.Sprintf("{%s}", col.JSONPath)); err != nil {
|
||||
return c, fmt.Errorf("unrecognized column definition %q", col.JSONPath)
|
||||
}
|
||||
path.AllowMissingKeys(true)
|
||||
|
||||
desc := fmt.Sprintf("Custom resource definition column (in JSONPath format): %s", col.JSONPath)
|
||||
if len(col.Description) > 0 {
|
||||
desc = col.Description
|
||||
}
|
||||
|
||||
c.additionalColumns = append(c.additionalColumns, path)
|
||||
c.headers = append(c.headers, metav1beta1.TableColumnDefinition{
|
||||
Name: col.Name,
|
||||
Type: col.Type,
|
||||
Format: col.Format,
|
||||
Description: desc,
|
||||
Priority: col.Priority,
|
||||
})
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type convertor struct {
|
||||
headers []metav1beta1.TableColumnDefinition
|
||||
additionalColumns []*jsonpath.JSONPath
|
||||
}
|
||||
|
||||
func (c *convertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
|
||||
table := &metav1beta1.Table{
|
||||
ColumnDefinitions: c.headers,
|
||||
}
|
||||
if m, err := meta.ListAccessor(obj); err == nil {
|
||||
table.ResourceVersion = m.GetResourceVersion()
|
||||
table.SelfLink = m.GetSelfLink()
|
||||
table.Continue = m.GetContinue()
|
||||
} else {
|
||||
if m, err := meta.CommonAccessor(obj); err == nil {
|
||||
table.ResourceVersion = m.GetResourceVersion()
|
||||
table.SelfLink = m.GetSelfLink()
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
buf := &bytes.Buffer{}
|
||||
table.Rows, err = metatable.MetaToTableRow(obj, func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) {
|
||||
cells := make([]interface{}, 1, 1+len(c.additionalColumns))
|
||||
cells[0] = name
|
||||
customHeaders := c.headers[1:]
|
||||
for i, column := range c.additionalColumns {
|
||||
results, err := column.FindResults(obj.(runtime.Unstructured).UnstructuredContent())
|
||||
if err != nil || len(results) == 0 || len(results[0]) == 0 {
|
||||
cells = append(cells, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// as we only support simple JSON path, we can assume to have only one result (or none, filtered out above)
|
||||
value := results[0][0].Interface()
|
||||
if customHeaders[i].Type == "string" {
|
||||
if err := column.PrintResults(buf, []reflect.Value{reflect.ValueOf(value)}); err == nil {
|
||||
cells = append(cells, buf.String())
|
||||
buf.Reset()
|
||||
} else {
|
||||
cells = append(cells, nil)
|
||||
}
|
||||
} else {
|
||||
cells = append(cells, cellForJSONValue(customHeaders[i].Type, value))
|
||||
}
|
||||
}
|
||||
return cells, nil
|
||||
})
|
||||
return table, err
|
||||
}
|
||||
|
||||
func cellForJSONValue(headerType string, value interface{}) interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch headerType {
|
||||
case "integer":
|
||||
switch typed := value.(type) {
|
||||
case int64:
|
||||
return typed
|
||||
case float64:
|
||||
return int64(typed)
|
||||
case json.Number:
|
||||
if i64, err := typed.Int64(); err == nil {
|
||||
return i64
|
||||
}
|
||||
}
|
||||
case "number":
|
||||
switch typed := value.(type) {
|
||||
case int64:
|
||||
return float64(typed)
|
||||
case float64:
|
||||
return typed
|
||||
case json.Number:
|
||||
if f, err := typed.Float64(); err == nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
case "boolean":
|
||||
if b, ok := value.(bool); ok {
|
||||
return b
|
||||
}
|
||||
case "string":
|
||||
if s, ok := value.(string); ok {
|
||||
return s
|
||||
}
|
||||
case "date":
|
||||
if typed, ok := value.(string); ok {
|
||||
var timestamp metav1.Time
|
||||
err := timestamp.UnmarshalQueryParameter(typed)
|
||||
if err != nil {
|
||||
return "<invalid>"
|
||||
}
|
||||
return metatable.ConvertToHumanReadableDateType(timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
68
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor_test.go
generated
vendored
Normal file
68
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor_test.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2018 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 tableconvertor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_cellForJSONValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
headerType string
|
||||
value interface{}
|
||||
want interface{}
|
||||
}{
|
||||
{"integer", int64(42), int64(42)},
|
||||
{"integer", float64(3.14), int64(3)},
|
||||
{"integer", true, nil},
|
||||
{"integer", "foo", nil},
|
||||
|
||||
{"number", int64(42), float64(42)},
|
||||
{"number", float64(3.14), float64(3.14)},
|
||||
{"number", true, nil},
|
||||
{"number", "foo", nil},
|
||||
|
||||
{"boolean", int64(42), nil},
|
||||
{"boolean", float64(3.14), nil},
|
||||
{"boolean", true, true},
|
||||
{"boolean", "foo", nil},
|
||||
|
||||
{"string", int64(42), nil},
|
||||
{"string", float64(3.14), nil},
|
||||
{"string", true, nil},
|
||||
{"string", "foo", "foo"},
|
||||
|
||||
{"date", int64(42), nil},
|
||||
{"date", float64(3.14), nil},
|
||||
{"date", true, nil},
|
||||
{"date", time.Now().Add(-time.Hour*12 - 30*time.Minute).UTC().Format(time.RFC3339), "12h"},
|
||||
{"date", time.Now().Add(+time.Hour*12 + 30*time.Minute).UTC().Format(time.RFC3339), "<invalid>"},
|
||||
{"date", "", "<unknown>"},
|
||||
|
||||
{"unknown", "foo", nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%#v of type %s", tt.value, tt.headerType), func(t *testing.T) {
|
||||
if got := cellForJSONValue(tt.headerType, tt.value); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("cellForJSONValue() = %#v, want %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
195
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
generated
vendored
Normal file
195
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
Copyright 2018 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 customresource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/validate"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
||||
)
|
||||
|
||||
type customResourceValidator struct {
|
||||
namespaceScoped bool
|
||||
kind schema.GroupVersionKind
|
||||
schemaValidator *validate.SchemaValidator
|
||||
statusSchemaValidator *validate.SchemaValidator
|
||||
}
|
||||
|
||||
func (a customResourceValidator) Validate(ctx context.Context, obj runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), u, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", u))}
|
||||
}
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
|
||||
if errs := a.ValidateTypeMeta(ctx, u); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
|
||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
||||
}
|
||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), u, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", u))}
|
||||
}
|
||||
objAccessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
oldAccessor, err := meta.Accessor(old)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
|
||||
if errs := a.ValidateTypeMeta(ctx, u); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
||||
}
|
||||
allErrs = append(allErrs, a.ValidateScaleSpec(ctx, u, scale)...)
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return field.ErrorList{field.Invalid(field.NewPath(""), u, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", u))}
|
||||
}
|
||||
objAccessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
oldAccessor, err := meta.Accessor(old)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
|
||||
}
|
||||
|
||||
if errs := a.ValidateTypeMeta(ctx, u); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))...)
|
||||
if err = apiservervalidation.ValidateCustomResource(u.UnstructuredContent(), a.schemaValidator); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(""), u.UnstructuredContent(), err.Error()))
|
||||
}
|
||||
allErrs = append(allErrs, a.ValidateScaleStatus(ctx, u, scale)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateTypeMeta(ctx context.Context, obj *unstructured.Unstructured) field.ErrorList {
|
||||
typeAccessor, err := meta.TypeAccessor(obj)
|
||||
if err != nil {
|
||||
return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())}
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
if typeAccessor.GetKind() != a.kind.Kind {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind)))
|
||||
}
|
||||
if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version)))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateScaleSpec(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
if scale == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
// validate specReplicas
|
||||
specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".") // ignore leading period
|
||||
specReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(specReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error()))
|
||||
} else if specReplicas < 0 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer"))
|
||||
} else if specReplicas > math.MaxInt32 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func (a customResourceValidator) ValidateScaleStatus(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
|
||||
if scale == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var allErrs field.ErrorList
|
||||
|
||||
// validate statusReplicas
|
||||
statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period
|
||||
statusReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(statusReplicasPath, ".")...)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error()))
|
||||
} else if statusReplicas < 0 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer"))
|
||||
} else if statusReplicas > math.MaxInt32 {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
|
||||
}
|
||||
|
||||
// validate labelSelector
|
||||
if scale.LabelSelectorPath != nil {
|
||||
labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period
|
||||
labelSelector, _, err := unstructured.NestedString(obj.UnstructuredContent(), strings.Split(labelSelectorPath, ".")...)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
179
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go
generated
vendored
Normal file
179
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
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 customresourcedefinition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
storageerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
)
|
||||
|
||||
// rest implements a RESTStorage for API services against etcd
|
||||
type REST struct {
|
||||
*genericregistry.Store
|
||||
}
|
||||
|
||||
// NewREST returns a RESTStorage object that will work against API services.
|
||||
func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST {
|
||||
strategy := NewStrategy(scheme)
|
||||
|
||||
store := &genericregistry.Store{
|
||||
NewFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinition{} },
|
||||
NewListFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinitionList{} },
|
||||
PredicateFunc: MatchCustomResourceDefinition,
|
||||
DefaultQualifiedResource: apiextensions.Resource("customresourcedefinitions"),
|
||||
|
||||
CreateStrategy: strategy,
|
||||
UpdateStrategy: strategy,
|
||||
DeleteStrategy: strategy,
|
||||
}
|
||||
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs}
|
||||
if err := store.CompleteWithOptions(options); err != nil {
|
||||
panic(err) // TODO: Propagate error up
|
||||
}
|
||||
return &REST{store}
|
||||
}
|
||||
|
||||
// Implement ShortNamesProvider
|
||||
var _ rest.ShortNamesProvider = &REST{}
|
||||
|
||||
// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
|
||||
func (r *REST) ShortNames() []string {
|
||||
return []string{"crd", "crds"}
|
||||
}
|
||||
|
||||
// Delete adds the CRD finalizer to the list
|
||||
func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
obj, err := r.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
crd := obj.(*apiextensions.CustomResourceDefinition)
|
||||
|
||||
// Ensure we have a UID precondition
|
||||
if options == nil {
|
||||
options = metav1.NewDeleteOptions(0)
|
||||
}
|
||||
if options.Preconditions == nil {
|
||||
options.Preconditions = &metav1.Preconditions{}
|
||||
}
|
||||
if options.Preconditions.UID == nil {
|
||||
options.Preconditions.UID = &crd.UID
|
||||
} else if *options.Preconditions.UID != crd.UID {
|
||||
err = apierrors.NewConflict(
|
||||
apiextensions.Resource("customresourcedefinitions"),
|
||||
name,
|
||||
fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", *options.Preconditions.UID, crd.UID),
|
||||
)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// upon first request to delete, add our finalizer and then delegate
|
||||
if crd.DeletionTimestamp.IsZero() {
|
||||
key, err := r.Store.KeyFunc(ctx, name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
preconditions := storage.Preconditions{UID: options.Preconditions.UID}
|
||||
|
||||
out := r.Store.NewFunc()
|
||||
err = r.Store.Storage.GuaranteedUpdate(
|
||||
ctx, key, out, false, &preconditions,
|
||||
storage.SimpleUpdate(func(existing runtime.Object) (runtime.Object, error) {
|
||||
existingCRD, ok := existing.(*apiextensions.CustomResourceDefinition)
|
||||
if !ok {
|
||||
// wrong type
|
||||
return nil, fmt.Errorf("expected *apiextensions.CustomResourceDefinition, got %v", existing)
|
||||
}
|
||||
|
||||
// Set the deletion timestamp if needed
|
||||
if existingCRD.DeletionTimestamp.IsZero() {
|
||||
now := metav1.Now()
|
||||
existingCRD.DeletionTimestamp = &now
|
||||
}
|
||||
|
||||
if !apiextensions.CRDHasFinalizer(existingCRD, apiextensions.CustomResourceCleanupFinalizer) {
|
||||
existingCRD.Finalizers = append(existingCRD.Finalizers, apiextensions.CustomResourceCleanupFinalizer)
|
||||
}
|
||||
// update the status condition too
|
||||
apiextensions.SetCRDCondition(existingCRD, apiextensions.CustomResourceDefinitionCondition{
|
||||
Type: apiextensions.Terminating,
|
||||
Status: apiextensions.ConditionTrue,
|
||||
Reason: "InstanceDeletionPending",
|
||||
Message: "CustomResourceDefinition marked for deletion; CustomResource deletion will begin soon",
|
||||
})
|
||||
return existingCRD, nil
|
||||
}),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
err = storageerr.InterpretGetError(err, apiextensions.Resource("customresourcedefinitions"), name)
|
||||
err = storageerr.InterpretUpdateError(err, apiextensions.Resource("customresourcedefinitions"), name)
|
||||
if _, ok := err.(*apierrors.StatusError); !ok {
|
||||
err = apierrors.NewInternalError(err)
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return out, false, nil
|
||||
}
|
||||
|
||||
return r.Store.Delete(ctx, name, options)
|
||||
}
|
||||
|
||||
// NewStatusREST makes a RESTStorage for status that has more limited options.
|
||||
// It is based on the original REST so that we can share the same underlying store
|
||||
func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST {
|
||||
statusStore := *rest.Store
|
||||
statusStore.CreateStrategy = nil
|
||||
statusStore.DeleteStrategy = nil
|
||||
statusStore.UpdateStrategy = NewStatusStrategy(scheme)
|
||||
return &StatusREST{store: &statusStore}
|
||||
}
|
||||
|
||||
type StatusREST struct {
|
||||
store *genericregistry.Store
|
||||
}
|
||||
|
||||
var _ = rest.Patcher(&StatusREST{})
|
||||
|
||||
func (r *StatusREST) New() runtime.Object {
|
||||
return &apiextensions.CustomResourceDefinition{}
|
||||
}
|
||||
|
||||
// Get retrieves the object from the storage. It is required to support Patch.
|
||||
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
return r.store.Get(ctx, name, options)
|
||||
}
|
||||
|
||||
// Update alters the status subset of an object.
|
||||
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool) (runtime.Object, bool, error) {
|
||||
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
|
||||
// subresources should never allow create on update.
|
||||
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false)
|
||||
}
|
202
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go
generated
vendored
Normal file
202
vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
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 customresourcedefinition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
|
||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||
)
|
||||
|
||||
// strategy implements behavior for CustomResources.
|
||||
type strategy struct {
|
||||
runtime.ObjectTyper
|
||||
names.NameGenerator
|
||||
}
|
||||
|
||||
func NewStrategy(typer runtime.ObjectTyper) strategy {
|
||||
return strategy{typer, names.SimpleNameGenerator}
|
||||
}
|
||||
|
||||
func (strategy) NamespaceScoped() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PrepareForCreate clears the status of a CustomResourceDefinition before creation.
|
||||
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
crd := obj.(*apiextensions.CustomResourceDefinition)
|
||||
crd.Status = apiextensions.CustomResourceDefinitionStatus{}
|
||||
crd.Generation = 1
|
||||
|
||||
// if the feature gate is disabled, drop the feature.
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) {
|
||||
crd.Spec.Validation = nil
|
||||
}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) {
|
||||
crd.Spec.Subresources = nil
|
||||
}
|
||||
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if v.Storage {
|
||||
if !apiextensions.IsStoredVersion(crd, v.Name) {
|
||||
crd.Status.StoredVersions = append(crd.Status.StoredVersions, v.Name)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||
func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||
newCRD := obj.(*apiextensions.CustomResourceDefinition)
|
||||
oldCRD := old.(*apiextensions.CustomResourceDefinition)
|
||||
newCRD.Status = oldCRD.Status
|
||||
|
||||
// Any changes to the spec increment the generation number, any changes to the
|
||||
// status should reflect the generation number of the corresponding object. We push
|
||||
// the burden of managing the status onto the clients because we can't (in general)
|
||||
// know here what version of spec the writer of the status has seen. It may seem like
|
||||
// we can at first -- since obj contains spec -- but in the future we will probably make
|
||||
// status its own object, and even if we don't, writes may be the result of a
|
||||
// read-update-write loop, so the contents of spec may not actually be the spec that
|
||||
// the controller has *seen*.
|
||||
if !apiequality.Semantic.DeepEqual(oldCRD.Spec, newCRD.Spec) {
|
||||
newCRD.Generation = oldCRD.Generation + 1
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) {
|
||||
newCRD.Spec.Validation = nil
|
||||
oldCRD.Spec.Validation = nil
|
||||
}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) {
|
||||
newCRD.Spec.Subresources = nil
|
||||
oldCRD.Spec.Subresources = nil
|
||||
}
|
||||
|
||||
for _, v := range newCRD.Spec.Versions {
|
||||
if v.Storage {
|
||||
if !apiextensions.IsStoredVersion(newCRD, v.Name) {
|
||||
newCRD.Status.StoredVersions = append(newCRD.Status.StoredVersions, v.Name)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates a new CustomResourceDefinition.
|
||||
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
return validation.ValidateCustomResourceDefinition(obj.(*apiextensions.CustomResourceDefinition))
|
||||
}
|
||||
|
||||
// AllowCreateOnUpdate is false for CustomResourceDefinition; this means a POST is
|
||||
// needed to create one.
|
||||
func (strategy) AllowCreateOnUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AllowUnconditionalUpdate is the default update policy for CustomResourceDefinition objects.
|
||||
func (strategy) AllowUnconditionalUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Canonicalize normalizes the object after validation.
|
||||
func (strategy) Canonicalize(obj runtime.Object) {
|
||||
}
|
||||
|
||||
// ValidateUpdate is the default update validation for an end user updating status.
|
||||
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return validation.ValidateCustomResourceDefinitionUpdate(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition))
|
||||
}
|
||||
|
||||
type statusStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
names.NameGenerator
|
||||
}
|
||||
|
||||
func NewStatusStrategy(typer runtime.ObjectTyper) statusStrategy {
|
||||
return statusStrategy{typer, names.SimpleNameGenerator}
|
||||
}
|
||||
|
||||
func (statusStrategy) NamespaceScoped() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||
newObj := obj.(*apiextensions.CustomResourceDefinition)
|
||||
oldObj := old.(*apiextensions.CustomResourceDefinition)
|
||||
newObj.Spec = oldObj.Spec
|
||||
|
||||
// Status updates are for only for updating status, not objectmeta.
|
||||
// TODO: Update after ResetObjectMetaForStatus is added to meta/v1.
|
||||
newObj.Labels = oldObj.Labels
|
||||
newObj.Annotations = oldObj.Annotations
|
||||
newObj.OwnerReferences = oldObj.OwnerReferences
|
||||
newObj.Generation = oldObj.Generation
|
||||
newObj.SelfLink = oldObj.SelfLink
|
||||
}
|
||||
|
||||
func (statusStrategy) AllowCreateOnUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (statusStrategy) AllowUnconditionalUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (statusStrategy) Canonicalize(obj runtime.Object) {
|
||||
}
|
||||
|
||||
func (statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return validation.ValidateUpdateCustomResourceDefinitionStatus(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition))
|
||||
}
|
||||
|
||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) {
|
||||
apiserver, ok := obj.(*apiextensions.CustomResourceDefinition)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf("given object is not a CustomResourceDefinition")
|
||||
}
|
||||
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
|
||||
// from etcd to clients of the apiserver only interested in specific labels/fields.
|
||||
func MatchCustomResourceDefinition(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
|
||||
return storage.SelectionPredicate{
|
||||
Label: label,
|
||||
Field: field,
|
||||
GetAttrs: GetAttrs,
|
||||
}
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionToSelectableFields returns a field set that represents the object.
|
||||
func CustomResourceDefinitionToSelectableFields(obj *apiextensions.CustomResourceDefinition) fields.Set {
|
||||
return generic.ObjectMetaFieldsSet(&obj.ObjectMeta, true)
|
||||
}
|
Reference in New Issue
Block a user