32
vendor/k8s.io/apiserver/pkg/registry/rest/OWNERS
generated
vendored
Normal file
32
vendor/k8s.io/apiserver/pkg/registry/rest/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
reviewers:
|
||||
- thockin
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- brendandburns
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- nikhiljindal
|
||||
- gmarek
|
||||
- justinsb
|
||||
- roberthbailey
|
||||
- ncdc
|
||||
- eparis
|
||||
- dims
|
||||
- hongchaodeng
|
||||
- krousey
|
||||
- jszczepkowski
|
||||
- euank
|
||||
- markturansky
|
||||
- fgrzadkowski
|
||||
- fabioy
|
||||
- ingvagabund
|
||||
- david-mcmahon
|
||||
- jianhuiz
|
||||
- nhlfr
|
||||
- feihujiang
|
||||
- sdminonne
|
||||
- goltermann
|
||||
- enj
|
||||
183
vendor/k8s.io/apiserver/pkg/registry/rest/create.go
generated
vendored
Normal file
183
vendor/k8s.io/apiserver/pkg/registry/rest/create.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
// RESTCreateStrategy defines the minimum validation, accepted input, and
|
||||
// name generation behavior to create an object that follows Kubernetes
|
||||
// API conventions.
|
||||
type RESTCreateStrategy interface {
|
||||
runtime.ObjectTyper
|
||||
// The name generator is used when the standard GenerateName field is set.
|
||||
// The NameGenerator will be invoked prior to validation.
|
||||
names.NameGenerator
|
||||
|
||||
// NamespaceScoped returns true if the object must be within a namespace.
|
||||
NamespaceScoped() bool
|
||||
// PrepareForCreate is invoked on create before validation to normalize
|
||||
// the object. For example: remove fields that are not to be persisted,
|
||||
// sort order-insensitive list fields, etc. This should not remove fields
|
||||
// whose presence would be considered a validation error.
|
||||
//
|
||||
// Often implemented as a type check and an initailization or clearing of
|
||||
// status. Clear the status because status changes are internal. External
|
||||
// callers of an api (users) should not be setting an initial status on
|
||||
// newly created objects.
|
||||
PrepareForCreate(ctx context.Context, obj runtime.Object)
|
||||
// Validate returns an ErrorList with validation errors or nil. Validate
|
||||
// is invoked after default fields in the object have been filled in
|
||||
// before the object is persisted. This method should not mutate the
|
||||
// object.
|
||||
Validate(ctx context.Context, obj runtime.Object) field.ErrorList
|
||||
// Canonicalize allows an object to be mutated into a canonical form. This
|
||||
// ensures that code that operates on these objects can rely on the common
|
||||
// form for things like comparison. Canonicalize is invoked after
|
||||
// validation has succeeded but before the object has been persisted.
|
||||
// This method may mutate the object. Often implemented as a type check or
|
||||
// empty method.
|
||||
Canonicalize(obj runtime.Object)
|
||||
}
|
||||
|
||||
// BeforeCreate ensures that common operations for all resources are performed on creation. It only returns
|
||||
// errors that can be converted to api.Status. It invokes PrepareForCreate, then GenerateName, then Validate.
|
||||
// It returns nil if the object should be created.
|
||||
func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime.Object) error {
|
||||
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return kerr
|
||||
}
|
||||
|
||||
if strategy.NamespaceScoped() {
|
||||
if !ValidNamespace(ctx, objectMeta) {
|
||||
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
|
||||
}
|
||||
} else if len(objectMeta.GetNamespace()) > 0 {
|
||||
objectMeta.SetNamespace(metav1.NamespaceNone)
|
||||
}
|
||||
objectMeta.SetDeletionTimestamp(nil)
|
||||
objectMeta.SetDeletionGracePeriodSeconds(nil)
|
||||
strategy.PrepareForCreate(ctx, obj)
|
||||
FillObjectMetaSystemFields(objectMeta)
|
||||
if len(objectMeta.GetGenerateName()) > 0 && len(objectMeta.GetName()) == 0 {
|
||||
objectMeta.SetName(strategy.GenerateName(objectMeta.GetGenerateName()))
|
||||
}
|
||||
|
||||
// Ensure Initializers are not set unless the feature is enabled
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
|
||||
objectMeta.SetInitializers(nil)
|
||||
}
|
||||
|
||||
// ClusterName is ignored and should not be saved
|
||||
if len(objectMeta.GetClusterName()) > 0 {
|
||||
objectMeta.SetClusterName("")
|
||||
}
|
||||
|
||||
if errs := strategy.Validate(ctx, obj); len(errs) > 0 {
|
||||
return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs)
|
||||
}
|
||||
|
||||
// Custom validation (including name validation) passed
|
||||
// Now run common validation on object meta
|
||||
// Do this *after* custom validation so that specific error messages are shown whenever possible
|
||||
if errs := genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 {
|
||||
return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs)
|
||||
}
|
||||
|
||||
strategy.Canonicalize(obj)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckGeneratedNameError checks whether an error that occurred creating a resource is due
|
||||
// to generation being unable to pick a valid name.
|
||||
func CheckGeneratedNameError(strategy RESTCreateStrategy, err error, obj runtime.Object) error {
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return kerr
|
||||
}
|
||||
|
||||
if len(objectMeta.GetGenerateName()) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.NewServerTimeoutForKind(kind.GroupKind(), "POST", 0)
|
||||
}
|
||||
|
||||
// objectMetaAndKind retrieves kind and ObjectMeta from a runtime object, or returns an error.
|
||||
func objectMetaAndKind(typer runtime.ObjectTyper, obj runtime.Object) (metav1.Object, schema.GroupVersionKind, error) {
|
||||
objectMeta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, schema.GroupVersionKind{}, errors.NewInternalError(err)
|
||||
}
|
||||
kinds, _, err := typer.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return nil, schema.GroupVersionKind{}, errors.NewInternalError(err)
|
||||
}
|
||||
return objectMeta, kinds[0], nil
|
||||
}
|
||||
|
||||
// NamespaceScopedStrategy has a method to tell if the object must be in a namespace.
|
||||
type NamespaceScopedStrategy interface {
|
||||
// NamespaceScoped returns if the object must be in a namespace.
|
||||
NamespaceScoped() bool
|
||||
}
|
||||
|
||||
// AdmissionToValidateObjectFunc converts validating admission to a rest validate object func
|
||||
func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes) ValidateObjectFunc {
|
||||
validatingAdmission, ok := admit.(admission.ValidationInterface)
|
||||
if !ok {
|
||||
return func(obj runtime.Object) error { return nil }
|
||||
}
|
||||
return func(obj runtime.Object) error {
|
||||
finalAttributes := admission.NewAttributesRecord(
|
||||
obj,
|
||||
staticAttributes.GetOldObject(),
|
||||
staticAttributes.GetKind(),
|
||||
staticAttributes.GetNamespace(),
|
||||
staticAttributes.GetName(),
|
||||
staticAttributes.GetResource(),
|
||||
staticAttributes.GetSubresource(),
|
||||
staticAttributes.GetOperation(),
|
||||
staticAttributes.IsDryRun(),
|
||||
staticAttributes.GetUserInfo(),
|
||||
)
|
||||
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
return validatingAdmission.Validate(finalAttributes)
|
||||
}
|
||||
}
|
||||
137
vendor/k8s.io/apiserver/pkg/registry/rest/delete.go
generated
vendored
Normal file
137
vendor/k8s.io/apiserver/pkg/registry/rest/delete.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// RESTDeleteStrategy defines deletion behavior on an object that follows Kubernetes
|
||||
// API conventions.
|
||||
type RESTDeleteStrategy interface {
|
||||
runtime.ObjectTyper
|
||||
}
|
||||
|
||||
type GarbageCollectionPolicy string
|
||||
|
||||
const (
|
||||
DeleteDependents GarbageCollectionPolicy = "DeleteDependents"
|
||||
OrphanDependents GarbageCollectionPolicy = "OrphanDependents"
|
||||
// Unsupported means that the resource knows that it cannot be GC'd, so the finalizers
|
||||
// should never be set in storage.
|
||||
Unsupported GarbageCollectionPolicy = "Unsupported"
|
||||
)
|
||||
|
||||
// GarbageCollectionDeleteStrategy must be implemented by the registry that wants to
|
||||
// orphan dependents by default.
|
||||
type GarbageCollectionDeleteStrategy interface {
|
||||
// DefaultGarbageCollectionPolicy returns the default garbage collection behavior.
|
||||
DefaultGarbageCollectionPolicy(ctx context.Context) GarbageCollectionPolicy
|
||||
}
|
||||
|
||||
// RESTGracefulDeleteStrategy must be implemented by the registry that supports
|
||||
// graceful deletion.
|
||||
type RESTGracefulDeleteStrategy interface {
|
||||
// CheckGracefulDelete should return true if the object can be gracefully deleted and set
|
||||
// any default values on the DeleteOptions.
|
||||
CheckGracefulDelete(ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) bool
|
||||
}
|
||||
|
||||
// BeforeDelete tests whether the object can be gracefully deleted.
|
||||
// If graceful is set, the object should be gracefully deleted. If gracefulPending
|
||||
// is set, the object has already been gracefully deleted (and the provided grace
|
||||
// period is longer than the time to deletion). An error is returned if the
|
||||
// condition cannot be checked or the gracePeriodSeconds is invalid. The options
|
||||
// argument may be updated with default values if graceful is true. Second place
|
||||
// where we set deletionTimestamp is pkg/registry/generic/registry/store.go.
|
||||
// This function is responsible for setting deletionTimestamp during gracefulDeletion,
|
||||
// other one for cascading deletions.
|
||||
func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) (graceful, gracefulPending bool, err error) {
|
||||
objectMeta, gvk, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return false, false, kerr
|
||||
}
|
||||
if errs := validation.ValidateDeleteOptions(options); len(errs) > 0 {
|
||||
return false, false, errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "DeleteOptions"}, "", errs)
|
||||
}
|
||||
// Checking the Preconditions here to fail early. They'll be enforced later on when we actually do the deletion, too.
|
||||
if options.Preconditions != nil && options.Preconditions.UID != nil && *options.Preconditions.UID != objectMeta.GetUID() {
|
||||
return false, false, errors.NewConflict(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.GetName(), fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *options.Preconditions.UID, objectMeta.GetUID()))
|
||||
}
|
||||
gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy)
|
||||
if !ok {
|
||||
// If we're not deleting gracefully there's no point in updating Generation, as we won't update
|
||||
// the obcject before deleting it.
|
||||
return false, false, nil
|
||||
}
|
||||
// if the object is already being deleted, no need to update generation.
|
||||
if objectMeta.GetDeletionTimestamp() != nil {
|
||||
// if we are already being deleted, we may only shorten the deletion grace period
|
||||
// this means the object was gracefully deleted previously but deletionGracePeriodSeconds was not set,
|
||||
// so we force deletion immediately
|
||||
// IMPORTANT:
|
||||
// The deletion operation happens in two phases.
|
||||
// 1. Update to set DeletionGracePeriodSeconds and DeletionTimestamp
|
||||
// 2. Delete the object from storage.
|
||||
// If the update succeeds, but the delete fails (network error, internal storage error, etc.),
|
||||
// a resource was previously left in a state that was non-recoverable. We
|
||||
// check if the existing stored resource has a grace period as 0 and if so
|
||||
// attempt to delete immediately in order to recover from this scenario.
|
||||
if objectMeta.GetDeletionGracePeriodSeconds() == nil || *objectMeta.GetDeletionGracePeriodSeconds() == 0 {
|
||||
return false, false, nil
|
||||
}
|
||||
// only a shorter grace period may be provided by a user
|
||||
if options.GracePeriodSeconds != nil {
|
||||
period := int64(*options.GracePeriodSeconds)
|
||||
if period >= *objectMeta.GetDeletionGracePeriodSeconds() {
|
||||
return false, true, nil
|
||||
}
|
||||
newDeletionTimestamp := metav1.NewTime(
|
||||
objectMeta.GetDeletionTimestamp().Add(-time.Second * time.Duration(*objectMeta.GetDeletionGracePeriodSeconds())).
|
||||
Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
|
||||
objectMeta.SetDeletionTimestamp(&newDeletionTimestamp)
|
||||
objectMeta.SetDeletionGracePeriodSeconds(&period)
|
||||
return true, false, nil
|
||||
}
|
||||
// graceful deletion is pending, do nothing
|
||||
options.GracePeriodSeconds = objectMeta.GetDeletionGracePeriodSeconds()
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
if !gracefulStrategy.CheckGracefulDelete(ctx, obj, options) {
|
||||
return false, false, nil
|
||||
}
|
||||
now := metav1.NewTime(metav1.Now().Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
|
||||
objectMeta.SetDeletionTimestamp(&now)
|
||||
objectMeta.SetDeletionGracePeriodSeconds(options.GracePeriodSeconds)
|
||||
// If it's the first graceful deletion we are going to set the DeletionTimestamp to non-nil.
|
||||
// Controllers of the object that's being deleted shouldn't take any nontrivial actions, hence its behavior changes.
|
||||
// Thus we need to bump object's Generation (if set). This handles generation bump during graceful deletion.
|
||||
// The bump for objects that don't support graceful deletion is handled in pkg/registry/generic/registry/store.go.
|
||||
if objectMeta.GetGeneration() > 0 {
|
||||
objectMeta.SetGeneration(objectMeta.GetGeneration() + 1)
|
||||
}
|
||||
return true, false, nil
|
||||
}
|
||||
18
vendor/k8s.io/apiserver/pkg/registry/rest/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/apiserver/pkg/registry/rest/doc.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package rest defines common logic around changes to Kubernetes-style resources.
|
||||
package rest // import "k8s.io/apiserver/pkg/registry/rest"
|
||||
34
vendor/k8s.io/apiserver/pkg/registry/rest/export.go
generated
vendored
Normal file
34
vendor/k8s.io/apiserver/pkg/registry/rest/export.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
43
vendor/k8s.io/apiserver/pkg/registry/rest/meta.go
generated
vendored
Normal file
43
vendor/k8s.io/apiserver/pkg/registry/rest/meta.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
// FillObjectMetaSystemFields populates fields that are managed by the system on ObjectMeta.
|
||||
func FillObjectMetaSystemFields(meta metav1.Object) {
|
||||
meta.SetCreationTimestamp(metav1.Now())
|
||||
meta.SetUID(uuid.NewUUID())
|
||||
meta.SetSelfLink("")
|
||||
}
|
||||
|
||||
// ValidNamespace returns false if the namespace on the context differs from
|
||||
// the resource. If the resource has no namespace, it is set to the value in
|
||||
// the context.
|
||||
func ValidNamespace(ctx context.Context, resource metav1.Object) bool {
|
||||
ns, ok := genericapirequest.NamespaceFrom(ctx)
|
||||
if len(resource.GetNamespace()) == 0 {
|
||||
resource.SetNamespace(ns)
|
||||
}
|
||||
return ns == resource.GetNamespace() && ok
|
||||
}
|
||||
336
vendor/k8s.io/apiserver/pkg/registry/rest/rest.go
generated
vendored
Normal file
336
vendor/k8s.io/apiserver/pkg/registry/rest/rest.go
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
//TODO:
|
||||
// Storage interfaces need to be separated into two groups; those that operate
|
||||
// on collections and those that operate on individually named items.
|
||||
// Collection interfaces:
|
||||
// (Method: Current -> Proposed)
|
||||
// GET: Lister -> CollectionGetter
|
||||
// WATCH: Watcher -> CollectionWatcher
|
||||
// CREATE: Creater -> CollectionCreater
|
||||
// DELETE: (n/a) -> CollectionDeleter
|
||||
// UPDATE: (n/a) -> CollectionUpdater
|
||||
//
|
||||
// Single item interfaces:
|
||||
// (Method: Current -> Proposed)
|
||||
// GET: Getter -> NamedGetter
|
||||
// WATCH: (n/a) -> NamedWatcher
|
||||
// CREATE: (n/a) -> NamedCreater
|
||||
// DELETE: Deleter -> NamedDeleter
|
||||
// UPDATE: Update -> NamedUpdater
|
||||
|
||||
// Storage is a generic interface for RESTful storage services.
|
||||
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
|
||||
// that objects may implement any of the below interfaces.
|
||||
type Storage interface {
|
||||
// New returns an empty object that can be used with Create and Update after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
}
|
||||
|
||||
// Scoper indicates what scope the resource is at. It must be specified.
|
||||
// It is usually provided automatically based on your strategy.
|
||||
type Scoper interface {
|
||||
// NamespaceScoped returns true if the storage is namespaced
|
||||
NamespaceScoped() bool
|
||||
}
|
||||
|
||||
// KindProvider specifies a different kind for its API than for its internal storage. This is necessary for external
|
||||
// objects that are not compiled into the api server. For such objects, there is no in-memory representation for
|
||||
// the object, so they must be represented as generic objects (e.g. runtime.Unknown), but when we present the object as part of
|
||||
// API discovery we want to present the specific kind, not the generic internal representation.
|
||||
type KindProvider interface {
|
||||
Kind() string
|
||||
}
|
||||
|
||||
// ShortNamesProvider is an interface for RESTful storage services. Delivers a list of short names for a resource. The list is used by kubectl to have short names representation of resources.
|
||||
type ShortNamesProvider interface {
|
||||
ShortNames() []string
|
||||
}
|
||||
|
||||
// CategoriesProvider allows a resource to specify which groups of resources (categories) it's part of. Categories can
|
||||
// be used by API clients to refer to a batch of resources by using a single name (e.g. "all" could translate to "pod,rc,svc,...").
|
||||
type CategoriesProvider interface {
|
||||
Categories() []string
|
||||
}
|
||||
|
||||
// GroupVersionKindProvider is used to specify a particular GroupVersionKind to discovery. This is used for polymorphic endpoints
|
||||
// which generally point to foreign versions. Scale refers to Scale.v1beta1.extensions for instance.
|
||||
// This trumps KindProvider since it is capable of providing the information required.
|
||||
// TODO KindProvider (only used by federation) should be removed and replaced with this, but that presents greater risk late in 1.8.
|
||||
type GroupVersionKindProvider interface {
|
||||
GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// Lister is an object that can retrieve resources that match the provided field and label criteria.
|
||||
type Lister interface {
|
||||
// NewList returns an empty object that can be used with the List call.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
NewList() runtime.Object
|
||||
// List selects resources in the storage which match to the selector. 'options' can be nil.
|
||||
List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
// returned error value err when the specified resource is not found.
|
||||
Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// GetterWithOptions is an object that retrieve a named RESTful resource and takes
|
||||
// additional options on the get request. It allows a caller to also receive the
|
||||
// subpath of the GET request.
|
||||
type GetterWithOptions interface {
|
||||
// Get finds a resource in the storage by name and returns it.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
// returned error value err when the specified resource is not found.
|
||||
// The options object passed to it is of the same type returned by the NewGetOptions
|
||||
// method.
|
||||
// TODO: Pass metav1.GetOptions.
|
||||
Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error)
|
||||
|
||||
// NewGetOptions returns an empty options object that will be used to pass
|
||||
// options to the Get method. It may return a bool and a string, if true, the
|
||||
// value of the request path below the object will be included as the named
|
||||
// string in the serialization of the runtime object. E.g., returning "path"
|
||||
// will convert the trailing request scheme value to "path" in the map[string][]string
|
||||
// passed to the converter.
|
||||
NewGetOptions() (runtime.Object, bool, string)
|
||||
}
|
||||
|
||||
type TableConvertor interface {
|
||||
ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error)
|
||||
}
|
||||
|
||||
// GracefulDeleter knows how to pass deletion options to allow delayed deletion of a
|
||||
// RESTful object.
|
||||
type GracefulDeleter interface {
|
||||
// Delete finds a resource in the storage and deletes it.
|
||||
// If options are provided, the resource will attempt to honor them or return an invalid
|
||||
// request error.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
// returned error value err when the specified resource is not found.
|
||||
// Delete *may* return the object that was deleted, or a status object indicating additional
|
||||
// information about deletion.
|
||||
// It also returns a boolean which is set to true if the resource was instantly
|
||||
// deleted or false if it will be deleted asynchronously.
|
||||
Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CollectionDeleter is an object that can delete a collection
|
||||
// of RESTful resources.
|
||||
type CollectionDeleter interface {
|
||||
// DeleteCollection selects all resources in the storage matching given 'listOptions'
|
||||
// and deletes them. If 'options' are provided, the resource will attempt to honor
|
||||
// them or return an invalid request error.
|
||||
// DeleteCollection may not be atomic - i.e. it may delete some objects and still
|
||||
// return an error after it. On success, returns a list of deleted objects.
|
||||
DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Creater is an object that can create an instance of a RESTful object.
|
||||
type Creater interface {
|
||||
// New returns an empty object that can be used with Create after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
|
||||
// Create creates a new version of a resource. If includeUninitialized is set, the object may be returned
|
||||
// without completing initialization.
|
||||
Create(ctx context.Context, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// NamedCreater is an object that can create an instance of a RESTful object using a name parameter.
|
||||
type NamedCreater interface {
|
||||
// New returns an empty object that can be used with Create after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
|
||||
// Create creates a new version of a resource. It expects a name parameter from the path.
|
||||
// This is needed for create operations on subresources which include the name of the parent
|
||||
// resource in the path. If includeUninitialized is set, the object may be returned without
|
||||
// completing initialization.
|
||||
Create(ctx context.Context, name string, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// UpdatedObjectInfo provides information about an updated object to an Updater.
|
||||
// It requires access to the old object in order to return the newly updated object.
|
||||
type UpdatedObjectInfo interface {
|
||||
// Returns preconditions built from the updated object, if applicable.
|
||||
// May return nil, or a preconditions object containing nil fields,
|
||||
// if no preconditions can be determined from the updated object.
|
||||
Preconditions() *metav1.Preconditions
|
||||
|
||||
// UpdatedObject returns the updated object, given a context and old object.
|
||||
// The only time an empty oldObj should be passed in is if a "create on update" is occurring (there is no oldObj).
|
||||
UpdatedObject(ctx context.Context, oldObj runtime.Object) (newObj runtime.Object, err error)
|
||||
}
|
||||
|
||||
// ValidateObjectFunc is a function to act on a given object. An error may be returned
|
||||
// if the hook cannot be completed. An ObjectFunc may NOT transform the provided
|
||||
// object.
|
||||
type ValidateObjectFunc func(obj runtime.Object) error
|
||||
|
||||
// ValidateAllObjectFunc is a "admit everything" instance of ValidateObjectFunc.
|
||||
func ValidateAllObjectFunc(obj runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateObjectUpdateFunc is a function to act on a given object and its predecessor.
|
||||
// An error may be returned if the hook cannot be completed. An UpdateObjectFunc
|
||||
// may NOT transform the provided object.
|
||||
type ValidateObjectUpdateFunc func(obj, old runtime.Object) error
|
||||
|
||||
// ValidateAllObjectUpdateFunc is a "admit everything" instance of ValidateObjectUpdateFunc.
|
||||
func ValidateAllObjectUpdateFunc(obj, old runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Updater is an object that can update an instance of a RESTful object.
|
||||
type Updater interface {
|
||||
// New returns an empty object that can be used with Update after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
|
||||
// Update finds a resource in the storage and updates it. Some implementations
|
||||
// may allow updates creates the object - they should set the created boolean
|
||||
// to true.
|
||||
Update(ctx context.Context, name string, objInfo UpdatedObjectInfo, createValidation ValidateObjectFunc, updateValidation ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CreaterUpdater is a storage object that must support both create and update.
|
||||
// Go prevents embedded interfaces that implement the same method.
|
||||
type CreaterUpdater interface {
|
||||
Creater
|
||||
Update(ctx context.Context, name string, objInfo UpdatedObjectInfo, createValidation ValidateObjectFunc, updateValidation ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CreaterUpdater must satisfy the Updater interface.
|
||||
var _ Updater = CreaterUpdater(nil)
|
||||
|
||||
// Patcher is a storage object that supports both get and update.
|
||||
type Patcher interface {
|
||||
Getter
|
||||
Updater
|
||||
}
|
||||
|
||||
// Watcher should be implemented by all Storage objects that
|
||||
// want to offer the ability to watch for changes through the watch api.
|
||||
type Watcher interface {
|
||||
// 'label' selects on labels; 'field' selects on the object's fields. Not all fields
|
||||
// are supported; an error should be returned if 'field' tries to select on a field that
|
||||
// isn't supported. 'resourceVersion' allows for continuing/starting a watch at a
|
||||
// particular version.
|
||||
Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// StandardStorage is an interface covering the common verbs. Provided for testing whether a
|
||||
// resource satisfies the normal storage methods. Use Storage when passing opaque storage objects.
|
||||
type StandardStorage interface {
|
||||
Getter
|
||||
Lister
|
||||
CreaterUpdater
|
||||
GracefulDeleter
|
||||
CollectionDeleter
|
||||
Watcher
|
||||
}
|
||||
|
||||
// Redirector know how to return a remote resource's location.
|
||||
type Redirector interface {
|
||||
// ResourceLocation should return the remote location of the given resource, and an optional transport to use to request it, or an error.
|
||||
ResourceLocation(ctx context.Context, id string) (remoteLocation *url.URL, transport http.RoundTripper, err error)
|
||||
}
|
||||
|
||||
// Responder abstracts the normal response behavior for a REST method and is passed to callers that
|
||||
// may wish to handle the response directly in some cases, but delegate to the normal error or object
|
||||
// behavior in other cases.
|
||||
type Responder interface {
|
||||
// Object writes the provided object to the response. Invoking this method multiple times is undefined.
|
||||
Object(statusCode int, obj runtime.Object)
|
||||
// Error writes the provided error to the response. This method may only be invoked once.
|
||||
Error(err error)
|
||||
}
|
||||
|
||||
// Connecter is a storage object that responds to a connection request.
|
||||
type Connecter interface {
|
||||
// Connect returns an http.Handler that will handle the request/response for a given API invocation.
|
||||
// The provided responder may be used for common API responses. The responder will write both status
|
||||
// code and body, so the ServeHTTP method should exit after invoking the responder. The Handler will
|
||||
// be used for a single API request and then discarded. The Responder is guaranteed to write to the
|
||||
// same http.ResponseWriter passed to ServeHTTP.
|
||||
Connect(ctx context.Context, id string, options runtime.Object, r Responder) (http.Handler, error)
|
||||
|
||||
// NewConnectOptions returns an empty options object that will be used to pass
|
||||
// options to the Connect method. If nil, then a nil options object is passed to
|
||||
// Connect. It may return a bool and a string. If true, the value of the request
|
||||
// path below the object will be included as the named string in the serialization
|
||||
// of the runtime object.
|
||||
NewConnectOptions() (runtime.Object, bool, string)
|
||||
|
||||
// ConnectMethods returns the list of HTTP methods handled by Connect
|
||||
ConnectMethods() []string
|
||||
}
|
||||
|
||||
// ResourceStreamer is an interface implemented by objects that prefer to be streamed from the server
|
||||
// instead of decoded directly.
|
||||
type ResourceStreamer interface {
|
||||
// InputStream should return an io.ReadCloser if the provided object supports streaming. The desired
|
||||
// api version and an accept header (may be empty) are passed to the call. If no error occurs,
|
||||
// the caller may return a flag indicating whether the result should be flushed as writes occur
|
||||
// and a content type string that indicates the type of the stream.
|
||||
// If a null stream is returned, a StatusNoContent response wil be generated.
|
||||
InputStream(ctx context.Context, apiVersion, acceptHeader string) (stream io.ReadCloser, flush bool, mimeType string, err error)
|
||||
}
|
||||
|
||||
// StorageMetadata is an optional interface that callers can implement to provide additional
|
||||
// information about their Storage objects.
|
||||
type StorageMetadata interface {
|
||||
// ProducesMIMETypes returns a list of the MIME types the specified HTTP verb (GET, POST, DELETE,
|
||||
// PATCH) can respond with.
|
||||
ProducesMIMETypes(verb string) []string
|
||||
|
||||
// ProducesObject returns an object the specified HTTP verb respond with. It will overwrite storage object if
|
||||
// it is not nil. Only the type of the return object matters, the value will be ignored.
|
||||
ProducesObject(verb string) interface{}
|
||||
}
|
||||
99
vendor/k8s.io/apiserver/pkg/registry/rest/table.go
generated
vendored
Normal file
99
vendor/k8s.io/apiserver/pkg/registry/rest/table.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type defaultTableConvertor struct {
|
||||
qualifiedResource schema.GroupResource
|
||||
}
|
||||
|
||||
// NewDefaultTableConvertor creates a default convertor for the provided resource.
|
||||
func NewDefaultTableConvertor(resource schema.GroupResource) TableConvertor {
|
||||
return defaultTableConvertor{qualifiedResource: resource}
|
||||
}
|
||||
|
||||
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
||||
|
||||
func (c defaultTableConvertor) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
|
||||
var table metav1beta1.Table
|
||||
fn := func(obj runtime.Object) error {
|
||||
m, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return errNotAcceptable{resource: c.qualifiedResource}
|
||||
}
|
||||
table.Rows = append(table.Rows, metav1beta1.TableRow{
|
||||
Cells: []interface{}{m.GetName(), m.GetCreationTimestamp().Time.UTC().Format(time.RFC3339)},
|
||||
Object: runtime.RawExtension{Object: obj},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case meta.IsListType(object):
|
||||
if err := meta.EachListItem(object, fn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
if err := fn(object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if m, err := meta.ListAccessor(object); err == nil {
|
||||
table.ResourceVersion = m.GetResourceVersion()
|
||||
table.SelfLink = m.GetSelfLink()
|
||||
table.Continue = m.GetContinue()
|
||||
} else {
|
||||
if m, err := meta.CommonAccessor(object); err == nil {
|
||||
table.ResourceVersion = m.GetResourceVersion()
|
||||
table.SelfLink = m.GetSelfLink()
|
||||
}
|
||||
}
|
||||
table.ColumnDefinitions = []metav1beta1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
|
||||
{Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]},
|
||||
}
|
||||
return &table, nil
|
||||
}
|
||||
|
||||
// errNotAcceptable indicates the resource doesn't support Table conversion
|
||||
type errNotAcceptable struct {
|
||||
resource schema.GroupResource
|
||||
}
|
||||
|
||||
func (e errNotAcceptable) Error() string {
|
||||
return fmt.Sprintf("the resource %s does not support being converted to a Table", e.resource)
|
||||
}
|
||||
|
||||
func (e errNotAcceptable) Status() metav1.Status {
|
||||
return metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotAcceptable,
|
||||
Reason: metav1.StatusReason("NotAcceptable"),
|
||||
Message: e.Error(),
|
||||
}
|
||||
}
|
||||
278
vendor/k8s.io/apiserver/pkg/registry/rest/update.go
generated
vendored
Normal file
278
vendor/k8s.io/apiserver/pkg/registry/rest/update.go
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
// RESTUpdateStrategy defines the minimum validation, accepted input, and
|
||||
// name generation behavior to update an object that follows Kubernetes
|
||||
// API conventions. A resource may have many UpdateStrategies, depending on
|
||||
// the call pattern in use.
|
||||
type RESTUpdateStrategy interface {
|
||||
runtime.ObjectTyper
|
||||
// NamespaceScoped returns true if the object must be within a namespace.
|
||||
NamespaceScoped() bool
|
||||
// AllowCreateOnUpdate returns true if the object can be created by a PUT.
|
||||
AllowCreateOnUpdate() bool
|
||||
// PrepareForUpdate is invoked on update before validation to normalize
|
||||
// the object. For example: remove fields that are not to be persisted,
|
||||
// sort order-insensitive list fields, etc. This should not remove fields
|
||||
// whose presence would be considered a validation error.
|
||||
PrepareForUpdate(ctx context.Context, obj, old runtime.Object)
|
||||
// ValidateUpdate is invoked after default fields in the object have been
|
||||
// filled in before the object is persisted. This method should not mutate
|
||||
// the object.
|
||||
ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList
|
||||
// Canonicalize allows an object to be mutated into a canonical form. This
|
||||
// ensures that code that operates on these objects can rely on the common
|
||||
// form for things like comparison. Canonicalize is invoked after
|
||||
// validation has succeeded but before the object has been persisted.
|
||||
// This method may mutate the object.
|
||||
Canonicalize(obj runtime.Object)
|
||||
// AllowUnconditionalUpdate returns true if the object can be updated
|
||||
// unconditionally (irrespective of the latest resource version), when
|
||||
// there is no resource version specified in the object.
|
||||
AllowUnconditionalUpdate() bool
|
||||
}
|
||||
|
||||
// TODO: add other common fields that require global validation.
|
||||
func validateCommonFields(obj, old runtime.Object, strategy RESTUpdateStrategy) (field.ErrorList, error) {
|
||||
allErrs := field.ErrorList{}
|
||||
objectMeta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get new object metadata: %v", err)
|
||||
}
|
||||
oldObjectMeta, err := meta.Accessor(old)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get old object metadata: %v", err)
|
||||
}
|
||||
allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata"))...)
|
||||
allErrs = append(allErrs, genericvalidation.ValidateObjectMetaAccessorUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...)
|
||||
|
||||
return allErrs, nil
|
||||
}
|
||||
|
||||
// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns
|
||||
// errors that can be converted to api.Status. It will invoke update validation with the provided existing
|
||||
// and updated objects.
|
||||
// It sets zero values only if the object does not have a zero value for the respective field.
|
||||
func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old runtime.Object) error {
|
||||
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return kerr
|
||||
}
|
||||
if strategy.NamespaceScoped() {
|
||||
if !ValidNamespace(ctx, objectMeta) {
|
||||
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
|
||||
}
|
||||
} else if len(objectMeta.GetNamespace()) > 0 {
|
||||
objectMeta.SetNamespace(metav1.NamespaceNone)
|
||||
}
|
||||
|
||||
// Ensure requests cannot update generation
|
||||
oldMeta, err := meta.Accessor(old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectMeta.SetGeneration(oldMeta.GetGeneration())
|
||||
|
||||
// Ensure Initializers are not set unless the feature is enabled
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
|
||||
oldMeta.SetInitializers(nil)
|
||||
objectMeta.SetInitializers(nil)
|
||||
}
|
||||
|
||||
strategy.PrepareForUpdate(ctx, obj, old)
|
||||
|
||||
// ClusterName is ignored and should not be saved
|
||||
if len(objectMeta.GetClusterName()) > 0 {
|
||||
objectMeta.SetClusterName("")
|
||||
}
|
||||
// Use the existing UID if none is provided
|
||||
if len(objectMeta.GetUID()) == 0 {
|
||||
objectMeta.SetUID(oldMeta.GetUID())
|
||||
}
|
||||
// ignore changes to timestamp
|
||||
if oldCreationTime := oldMeta.GetCreationTimestamp(); !oldCreationTime.IsZero() {
|
||||
objectMeta.SetCreationTimestamp(oldMeta.GetCreationTimestamp())
|
||||
}
|
||||
// an update can never remove/change a deletion timestamp
|
||||
if !oldMeta.GetDeletionTimestamp().IsZero() {
|
||||
objectMeta.SetDeletionTimestamp(oldMeta.GetDeletionTimestamp())
|
||||
}
|
||||
// an update can never remove/change grace period seconds
|
||||
if oldMeta.GetDeletionGracePeriodSeconds() != nil && objectMeta.GetDeletionGracePeriodSeconds() == nil {
|
||||
objectMeta.SetDeletionGracePeriodSeconds(oldMeta.GetDeletionGracePeriodSeconds())
|
||||
}
|
||||
|
||||
// Ensure some common fields, like UID, are validated for all resources.
|
||||
errs, err := validateCommonFields(obj, old, strategy)
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...)
|
||||
if len(errs) > 0 {
|
||||
return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs)
|
||||
}
|
||||
|
||||
strategy.Canonicalize(obj)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransformFunc is a function to transform and return newObj
|
||||
type TransformFunc func(ctx context.Context, newObj runtime.Object, oldObj runtime.Object) (transformedNewObj runtime.Object, err error)
|
||||
|
||||
// defaultUpdatedObjectInfo implements UpdatedObjectInfo
|
||||
type defaultUpdatedObjectInfo struct {
|
||||
// obj is the updated object
|
||||
obj runtime.Object
|
||||
|
||||
// transformers is an optional list of transforming functions that modify or
|
||||
// replace obj using information from the context, old object, or other sources.
|
||||
transformers []TransformFunc
|
||||
}
|
||||
|
||||
// DefaultUpdatedObjectInfo returns an UpdatedObjectInfo impl based on the specified object.
|
||||
func DefaultUpdatedObjectInfo(obj runtime.Object, transformers ...TransformFunc) UpdatedObjectInfo {
|
||||
return &defaultUpdatedObjectInfo{obj, transformers}
|
||||
}
|
||||
|
||||
// Preconditions satisfies the UpdatedObjectInfo interface.
|
||||
func (i *defaultUpdatedObjectInfo) Preconditions() *metav1.Preconditions {
|
||||
// Attempt to get the UID out of the object
|
||||
accessor, err := meta.Accessor(i.obj)
|
||||
if err != nil {
|
||||
// If no UID can be read, no preconditions are possible
|
||||
return nil
|
||||
}
|
||||
|
||||
// If empty, no preconditions needed
|
||||
uid := accessor.GetUID()
|
||||
if len(uid) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &metav1.Preconditions{UID: &uid}
|
||||
}
|
||||
|
||||
// UpdatedObject satisfies the UpdatedObjectInfo interface.
|
||||
// It returns a copy of the held obj, passed through any configured transformers.
|
||||
func (i *defaultUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
|
||||
var err error
|
||||
// Start with the configured object
|
||||
newObj := i.obj
|
||||
|
||||
// If the original is non-nil (might be nil if the first transformer builds the object from the oldObj), make a copy,
|
||||
// so we don't return the original. BeforeUpdate can mutate the returned object, doing things like clearing ResourceVersion.
|
||||
// If we're re-called, we need to be able to return the pristine version.
|
||||
if newObj != nil {
|
||||
newObj = newObj.DeepCopyObject()
|
||||
}
|
||||
|
||||
// Allow any configured transformers to update the new object
|
||||
for _, transformer := range i.transformers {
|
||||
newObj, err = transformer(ctx, newObj, oldObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// wrappedUpdatedObjectInfo allows wrapping an existing objInfo and
|
||||
// chaining additional transformations/checks on the result of UpdatedObject()
|
||||
type wrappedUpdatedObjectInfo struct {
|
||||
// obj is the updated object
|
||||
objInfo UpdatedObjectInfo
|
||||
|
||||
// transformers is an optional list of transforming functions that modify or
|
||||
// replace obj using information from the context, old object, or other sources.
|
||||
transformers []TransformFunc
|
||||
}
|
||||
|
||||
// WrapUpdatedObjectInfo returns an UpdatedObjectInfo impl that delegates to
|
||||
// the specified objInfo, then calls the passed transformers
|
||||
func WrapUpdatedObjectInfo(objInfo UpdatedObjectInfo, transformers ...TransformFunc) UpdatedObjectInfo {
|
||||
return &wrappedUpdatedObjectInfo{objInfo, transformers}
|
||||
}
|
||||
|
||||
// Preconditions satisfies the UpdatedObjectInfo interface.
|
||||
func (i *wrappedUpdatedObjectInfo) Preconditions() *metav1.Preconditions {
|
||||
return i.objInfo.Preconditions()
|
||||
}
|
||||
|
||||
// UpdatedObject satisfies the UpdatedObjectInfo interface.
|
||||
// It delegates to the wrapped objInfo and passes the result through any configured transformers.
|
||||
func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
|
||||
newObj, err := i.objInfo.UpdatedObject(ctx, oldObj)
|
||||
if err != nil {
|
||||
return newObj, err
|
||||
}
|
||||
|
||||
// Allow any configured transformers to update the new object or error
|
||||
for _, transformer := range i.transformers {
|
||||
newObj, err = transformer(ctx, newObj, oldObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// AdmissionToValidateObjectUpdateFunc converts validating admission to a rest validate object update func
|
||||
func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttributes admission.Attributes) ValidateObjectUpdateFunc {
|
||||
validatingAdmission, ok := admit.(admission.ValidationInterface)
|
||||
if !ok {
|
||||
return func(obj, old runtime.Object) error { return nil }
|
||||
}
|
||||
return func(obj, old runtime.Object) error {
|
||||
finalAttributes := admission.NewAttributesRecord(
|
||||
obj,
|
||||
old,
|
||||
staticAttributes.GetKind(),
|
||||
staticAttributes.GetNamespace(),
|
||||
staticAttributes.GetName(),
|
||||
staticAttributes.GetResource(),
|
||||
staticAttributes.GetSubresource(),
|
||||
staticAttributes.GetOperation(),
|
||||
staticAttributes.IsDryRun(),
|
||||
staticAttributes.GetUserInfo(),
|
||||
)
|
||||
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
return validatingAdmission.Validate(finalAttributes)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user