use istio client-go library instead of knative (#1661)
use istio client-go library instead of knative bump kubernetes dependency version change code coverage to codecov
This commit is contained in:
103
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
103
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
@@ -24,6 +24,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
@@ -34,6 +35,7 @@ type attributesRecord struct {
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
operation Operation
|
||||
options runtime.Object
|
||||
dryRun bool
|
||||
object runtime.Object
|
||||
oldObject runtime.Object
|
||||
@@ -41,22 +43,31 @@ type attributesRecord struct {
|
||||
|
||||
// other elements are always accessed in single goroutine.
|
||||
// But ValidatingAdmissionWebhook add annotations concurrently.
|
||||
annotations map[string]string
|
||||
annotations map[string]annotation
|
||||
annotationsLock sync.RWMutex
|
||||
|
||||
reinvocationContext ReinvocationContext
|
||||
}
|
||||
|
||||
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, dryRun bool, userInfo user.Info) Attributes {
|
||||
type annotation struct {
|
||||
level auditinternal.Level
|
||||
value string
|
||||
}
|
||||
|
||||
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes {
|
||||
return &attributesRecord{
|
||||
kind: kind,
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
resource: resource,
|
||||
subresource: subresource,
|
||||
operation: operation,
|
||||
dryRun: dryRun,
|
||||
object: object,
|
||||
oldObject: oldObject,
|
||||
userInfo: userInfo,
|
||||
kind: kind,
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
resource: resource,
|
||||
subresource: subresource,
|
||||
operation: operation,
|
||||
options: operationOptions,
|
||||
dryRun: dryRun,
|
||||
object: object,
|
||||
oldObject: oldObject,
|
||||
userInfo: userInfo,
|
||||
reinvocationContext: &reinvocationContext{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +95,10 @@ func (record *attributesRecord) GetOperation() Operation {
|
||||
return record.operation
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOperationOptions() runtime.Object {
|
||||
return record.options
|
||||
}
|
||||
|
||||
func (record *attributesRecord) IsDryRun() bool {
|
||||
return record.dryRun
|
||||
}
|
||||
@@ -102,7 +117,7 @@ func (record *attributesRecord) GetUserInfo() user.Info {
|
||||
|
||||
// getAnnotations implements privateAnnotationsGetter.It's a private method used
|
||||
// by WithAudit decorator.
|
||||
func (record *attributesRecord) getAnnotations() map[string]string {
|
||||
func (record *attributesRecord) getAnnotations(maxLevel auditinternal.Level) map[string]string {
|
||||
record.annotationsLock.RLock()
|
||||
defer record.annotationsLock.RUnlock()
|
||||
|
||||
@@ -111,29 +126,79 @@ func (record *attributesRecord) getAnnotations() map[string]string {
|
||||
}
|
||||
cp := make(map[string]string, len(record.annotations))
|
||||
for key, value := range record.annotations {
|
||||
cp[key] = value
|
||||
if value.level.Less(maxLevel) || value.level == maxLevel {
|
||||
cp[key] = value.value
|
||||
}
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
||||
// AddAnnotation adds an annotation to attributesRecord with Metadata audit level
|
||||
func (record *attributesRecord) AddAnnotation(key, value string) error {
|
||||
return record.AddAnnotationWithLevel(key, value, auditinternal.LevelMetadata)
|
||||
}
|
||||
|
||||
func (record *attributesRecord) AddAnnotationWithLevel(key, value string, level auditinternal.Level) error {
|
||||
if err := checkKeyFormat(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if level.Less(auditinternal.LevelMetadata) {
|
||||
return fmt.Errorf("admission annotations are not allowed to be set at audit level lower than Metadata, key: %q, level: %s", key, level)
|
||||
}
|
||||
record.annotationsLock.Lock()
|
||||
defer record.annotationsLock.Unlock()
|
||||
|
||||
if record.annotations == nil {
|
||||
record.annotations = make(map[string]string)
|
||||
record.annotations = make(map[string]annotation)
|
||||
}
|
||||
if v, ok := record.annotations[key]; ok && v != value {
|
||||
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %q, new value:%q", key, record.annotations[key], value)
|
||||
annotation := annotation{level: level, value: value}
|
||||
if v, ok := record.annotations[key]; ok && v != annotation {
|
||||
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %v, new value: %v", key, record.annotations[key], annotation)
|
||||
}
|
||||
record.annotations[key] = value
|
||||
record.annotations[key] = annotation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetReinvocationContext() ReinvocationContext {
|
||||
return record.reinvocationContext
|
||||
}
|
||||
|
||||
type reinvocationContext struct {
|
||||
// isReinvoke is true when admission plugins are being reinvoked
|
||||
isReinvoke bool
|
||||
// reinvokeRequested is true when an admission plugin requested a re-invocation of the chain
|
||||
reinvokeRequested bool
|
||||
// values stores reinvoke context values per plugin.
|
||||
values map[string]interface{}
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) IsReinvoke() bool {
|
||||
return rc.isReinvoke
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetIsReinvoke() {
|
||||
rc.isReinvoke = true
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) ShouldReinvoke() bool {
|
||||
return rc.reinvokeRequested
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetShouldReinvoke() {
|
||||
rc.reinvokeRequested = true
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetValue(plugin string, v interface{}) {
|
||||
if rc.values == nil {
|
||||
rc.values = map[string]interface{}{}
|
||||
}
|
||||
rc.values[plugin] = v
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) Value(plugin string) interface{} {
|
||||
return rc.values[plugin]
|
||||
}
|
||||
|
||||
func checkKeyFormat(key string) error {
|
||||
parts := strings.Split(key, "/")
|
||||
if len(parts) != 2 {
|
||||
|
||||
20
vendor/k8s.io/apiserver/pkg/admission/audit.go
generated
vendored
20
vendor/k8s.io/apiserver/pkg/admission/audit.go
generated
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
@@ -44,7 +45,7 @@ func WithAudit(i Interface, ae *auditinternal.Event) Interface {
|
||||
return &auditHandler{i, ae}
|
||||
}
|
||||
|
||||
func (handler auditHandler) Admit(a Attributes) error {
|
||||
func (handler auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if !handler.Interface.Handles(a.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
@@ -53,13 +54,13 @@ func (handler auditHandler) Admit(a Attributes) error {
|
||||
}
|
||||
var err error
|
||||
if mutator, ok := handler.Interface.(MutationInterface); ok {
|
||||
err = mutator.Admit(a)
|
||||
err = mutator.Admit(ctx, a, o)
|
||||
handler.logAnnotations(a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (handler auditHandler) Validate(a Attributes) error {
|
||||
func (handler auditHandler) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if !handler.Interface.Handles(a.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func (handler auditHandler) Validate(a Attributes) error {
|
||||
}
|
||||
var err error
|
||||
if validator, ok := handler.Interface.(ValidationInterface); ok {
|
||||
err = validator.Validate(a)
|
||||
err = validator.Validate(ctx, a, o)
|
||||
handler.logAnnotations(a)
|
||||
}
|
||||
return err
|
||||
@@ -84,11 +85,18 @@ func ensureAnnotationGetter(a Attributes) error {
|
||||
}
|
||||
|
||||
func (handler auditHandler) logAnnotations(a Attributes) {
|
||||
if handler.ae == nil {
|
||||
return
|
||||
}
|
||||
switch a := a.(type) {
|
||||
case privateAnnotationsGetter:
|
||||
audit.LogAnnotations(handler.ae, a.getAnnotations())
|
||||
for key, value := range a.getAnnotations(handler.ae.Level) {
|
||||
audit.LogAnnotation(handler.ae, key, value)
|
||||
}
|
||||
case AnnotationsGetter:
|
||||
audit.LogAnnotations(handler.ae, a.GetAnnotations())
|
||||
for key, value := range a.GetAnnotations(handler.ae.Level) {
|
||||
audit.LogAnnotation(handler.ae, key, value)
|
||||
}
|
||||
default:
|
||||
// this will never happen, because we have already checked it in ensureAnnotationGetter
|
||||
}
|
||||
|
||||
10
vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
@@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
package admission
|
||||
|
||||
import "context"
|
||||
|
||||
// chainAdmissionHandler is an instance of admission.NamedHandler that performs admission control using
|
||||
// a chain of admission handlers
|
||||
type chainAdmissionHandler []Interface
|
||||
@@ -26,13 +28,13 @@ func NewChainHandler(handlers ...Interface) chainAdmissionHandler {
|
||||
}
|
||||
|
||||
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
|
||||
func (admissionHandler chainAdmissionHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
if mutator, ok := handler.(MutationInterface); ok {
|
||||
err := mutator.Admit(a)
|
||||
err := mutator.Admit(ctx, a, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,13 +44,13 @@ func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
|
||||
}
|
||||
|
||||
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||
func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error {
|
||||
func (admissionHandler chainAdmissionHandler) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
if validator, ok := handler.(ValidationInterface); ok {
|
||||
err := validator.Validate(a)
|
||||
err := validator.Validate(ctx, a, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
3
vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
3
vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
@@ -87,7 +87,6 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, con
|
||||
}
|
||||
return configProvider{
|
||||
config: decodedConfig,
|
||||
scheme: configScheme,
|
||||
}, nil
|
||||
}
|
||||
// we got an error where the decode wasn't related to a missing type
|
||||
@@ -127,13 +126,11 @@ func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, con
|
||||
}
|
||||
return configProvider{
|
||||
config: internalConfig,
|
||||
scheme: configScheme,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type configProvider struct {
|
||||
config *apiserver.AdmissionConfiguration
|
||||
scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
|
||||
|
||||
88
vendor/k8s.io/apiserver/pkg/admission/configuration/initializer_manager.go
generated
vendored
88
vendor/k8s.io/apiserver/pkg/admission/configuration/initializer_manager.go
generated
vendored
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
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 configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type InitializerConfigurationLister interface {
|
||||
List(opts metav1.ListOptions) (*v1alpha1.InitializerConfigurationList, error)
|
||||
}
|
||||
|
||||
type InitializerConfigurationManager struct {
|
||||
*poller
|
||||
}
|
||||
|
||||
func NewInitializerConfigurationManager(c InitializerConfigurationLister) *InitializerConfigurationManager {
|
||||
getFn := func() (runtime.Object, error) {
|
||||
list, err := c.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) || errors.IsForbidden(err) {
|
||||
klog.V(5).Infof("Initializers are disabled due to an error: %v", err)
|
||||
return nil, ErrDisabled
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return mergeInitializerConfigurations(list), nil
|
||||
}
|
||||
return &InitializerConfigurationManager{
|
||||
newPoller(getFn),
|
||||
}
|
||||
}
|
||||
|
||||
// Initializers returns the merged InitializerConfiguration.
|
||||
func (im *InitializerConfigurationManager) Initializers() (*v1alpha1.InitializerConfiguration, error) {
|
||||
configuration, err := im.poller.configuration()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initializerConfiguration, ok := configuration.(*v1alpha1.InitializerConfiguration)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected type %v, got type %v", reflect.TypeOf(initializerConfiguration), reflect.TypeOf(configuration))
|
||||
}
|
||||
return initializerConfiguration, nil
|
||||
}
|
||||
|
||||
func (im *InitializerConfigurationManager) Run(stopCh <-chan struct{}) {
|
||||
im.poller.Run(stopCh)
|
||||
}
|
||||
|
||||
func mergeInitializerConfigurations(initializerConfigurationList *v1alpha1.InitializerConfigurationList) *v1alpha1.InitializerConfiguration {
|
||||
configurations := initializerConfigurationList.Items
|
||||
sort.SliceStable(configurations, InitializerConfigurationSorter(configurations).ByName)
|
||||
var ret v1alpha1.InitializerConfiguration
|
||||
for _, c := range configurations {
|
||||
ret.Initializers = append(ret.Initializers, c.Initializers...)
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
type InitializerConfigurationSorter []v1alpha1.InitializerConfiguration
|
||||
|
||||
func (a InitializerConfigurationSorter) ByName(i, j int) bool {
|
||||
return a[i].Name < a[j].Name
|
||||
}
|
||||
23
vendor/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go
generated
vendored
23
vendor/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go
generated
vendored
@@ -24,6 +24,7 @@ import (
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
|
||||
@@ -48,7 +49,7 @@ func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) g
|
||||
}
|
||||
|
||||
// Start with an empty list
|
||||
manager.configuration.Store(&v1beta1.MutatingWebhookConfiguration{})
|
||||
manager.configuration.Store([]webhook.WebhookAccessor{})
|
||||
|
||||
// On any change, rebuild the config
|
||||
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
@@ -61,8 +62,8 @@ func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) g
|
||||
}
|
||||
|
||||
// Webhooks returns the merged MutatingWebhookConfiguration.
|
||||
func (m *mutatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook {
|
||||
return m.configuration.Load().(*v1beta1.MutatingWebhookConfiguration).Webhooks
|
||||
func (m *mutatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
|
||||
return m.configuration.Load().([]webhook.WebhookAccessor)
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookConfigurationManager) HasSynced() bool {
|
||||
@@ -78,16 +79,24 @@ func (m *mutatingWebhookConfigurationManager) updateConfiguration() {
|
||||
m.configuration.Store(mergeMutatingWebhookConfigurations(configurations))
|
||||
}
|
||||
|
||||
func mergeMutatingWebhookConfigurations(configurations []*v1beta1.MutatingWebhookConfiguration) *v1beta1.MutatingWebhookConfiguration {
|
||||
var ret v1beta1.MutatingWebhookConfiguration
|
||||
func mergeMutatingWebhookConfigurations(configurations []*v1beta1.MutatingWebhookConfiguration) []webhook.WebhookAccessor {
|
||||
// The internal order of webhooks for each configuration is provided by the user
|
||||
// but configurations themselves can be in any order. As we are going to run these
|
||||
// webhooks in serial, they are sorted here to have a deterministic order.
|
||||
sort.SliceStable(configurations, MutatingWebhookConfigurationSorter(configurations).ByName)
|
||||
accessors := []webhook.WebhookAccessor{}
|
||||
for _, c := range configurations {
|
||||
ret.Webhooks = append(ret.Webhooks, c.Webhooks...)
|
||||
// webhook names are not validated for uniqueness, so we check for duplicates and
|
||||
// add a int suffix to distinguish between them
|
||||
names := map[string]int{}
|
||||
for i := range c.Webhooks {
|
||||
n := c.Webhooks[i].Name
|
||||
uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
|
||||
names[n]++
|
||||
accessors = append(accessors, webhook.NewMutatingWebhookAccessor(uid, c.Name, &c.Webhooks[i]))
|
||||
}
|
||||
}
|
||||
return &ret
|
||||
return accessors
|
||||
}
|
||||
|
||||
type MutatingWebhookConfigurationSorter []*v1beta1.MutatingWebhookConfiguration
|
||||
|
||||
25
vendor/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go
generated
vendored
25
vendor/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go
generated
vendored
@@ -24,6 +24,7 @@ import (
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
|
||||
@@ -48,7 +49,7 @@ func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory)
|
||||
}
|
||||
|
||||
// Start with an empty list
|
||||
manager.configuration.Store(&v1beta1.ValidatingWebhookConfiguration{})
|
||||
manager.configuration.Store([]webhook.WebhookAccessor{})
|
||||
|
||||
// On any change, rebuild the config
|
||||
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
@@ -61,8 +62,8 @@ func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory)
|
||||
}
|
||||
|
||||
// Webhooks returns the merged ValidatingWebhookConfiguration.
|
||||
func (v *validatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook {
|
||||
return v.configuration.Load().(*v1beta1.ValidatingWebhookConfiguration).Webhooks
|
||||
func (v *validatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
|
||||
return v.configuration.Load().([]webhook.WebhookAccessor)
|
||||
}
|
||||
|
||||
// HasSynced returns true if the shared informers have synced.
|
||||
@@ -79,15 +80,21 @@ func (v *validatingWebhookConfigurationManager) updateConfiguration() {
|
||||
v.configuration.Store(mergeValidatingWebhookConfigurations(configurations))
|
||||
}
|
||||
|
||||
func mergeValidatingWebhookConfigurations(
|
||||
configurations []*v1beta1.ValidatingWebhookConfiguration,
|
||||
) *v1beta1.ValidatingWebhookConfiguration {
|
||||
func mergeValidatingWebhookConfigurations(configurations []*v1beta1.ValidatingWebhookConfiguration) []webhook.WebhookAccessor {
|
||||
sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName)
|
||||
var ret v1beta1.ValidatingWebhookConfiguration
|
||||
accessors := []webhook.WebhookAccessor{}
|
||||
for _, c := range configurations {
|
||||
ret.Webhooks = append(ret.Webhooks, c.Webhooks...)
|
||||
// webhook names are not validated for uniqueness, so we check for duplicates and
|
||||
// add a int suffix to distinguish between them
|
||||
names := map[string]int{}
|
||||
for i := range c.Webhooks {
|
||||
n := c.Webhooks[i].Name
|
||||
uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
|
||||
names[n]++
|
||||
accessors = append(accessors, webhook.NewValidatingWebhookAccessor(uid, c.Name, &c.Webhooks[i]))
|
||||
}
|
||||
}
|
||||
return &ret
|
||||
return accessors
|
||||
}
|
||||
|
||||
type ValidatingWebhookConfigurationSorter []*v1beta1.ValidatingWebhookConfiguration
|
||||
|
||||
8
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package initializer
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/informers"
|
||||
@@ -28,7 +27,6 @@ type pluginInitializer struct {
|
||||
externalClient kubernetes.Interface
|
||||
externalInformers informers.SharedInformerFactory
|
||||
authorizer authorizer.Authorizer
|
||||
scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// New creates an instance of admission plugins initializer.
|
||||
@@ -37,13 +35,11 @@ func New(
|
||||
extClientset kubernetes.Interface,
|
||||
extInformers informers.SharedInformerFactory,
|
||||
authz authorizer.Authorizer,
|
||||
scheme *runtime.Scheme,
|
||||
) pluginInitializer {
|
||||
return pluginInitializer{
|
||||
externalClient: extClientset,
|
||||
externalInformers: extInformers,
|
||||
authorizer: authz,
|
||||
scheme: scheme,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +57,6 @@ func (i pluginInitializer) Initialize(plugin admission.Interface) {
|
||||
if wants, ok := plugin.(WantsAuthorizer); ok {
|
||||
wants.SetAuthorizer(i.authorizer)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsScheme); ok {
|
||||
wants.SetScheme(i.scheme)
|
||||
}
|
||||
}
|
||||
|
||||
var _ admission.PluginInitializer = pluginInitializer{}
|
||||
|
||||
7
vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
7
vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package initializer
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/informers"
|
||||
@@ -41,9 +40,3 @@ type WantsAuthorizer interface {
|
||||
SetAuthorizer(authorizer.Authorizer)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsScheme defines a function that accepts runtime.Scheme for admission plugins that need it.
|
||||
type WantsScheme interface {
|
||||
SetScheme(*runtime.Scheme)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
58
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
58
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
@@ -17,10 +17,12 @@ limitations under the License.
|
||||
package admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
@@ -41,6 +43,8 @@ type Attributes interface {
|
||||
GetSubresource() string
|
||||
// GetOperation is the operation being performed
|
||||
GetOperation() Operation
|
||||
// GetOperationOptions is the options for the operation being performed
|
||||
GetOperationOptions() runtime.Object
|
||||
// IsDryRun indicates that modifications will definitely not be persisted for this request. This is to prevent
|
||||
// admission controllers with side effects and a method of reconciliation from being overwhelmed.
|
||||
// However, a value of false for this does not mean that the modification will be persisted, because it
|
||||
@@ -59,18 +63,60 @@ type Attributes interface {
|
||||
// "podsecuritypolicy" is the name of the plugin, "admission.k8s.io" is the name of the organization, "admit-policy" is the key name.
|
||||
// An error is returned if the format of key is invalid. When trying to overwrite annotation with a new value, an error is returned.
|
||||
// Both ValidationInterface and MutationInterface are allowed to add Annotations.
|
||||
// By default, an annotation gets logged into audit event if the request's audit level is greater or
|
||||
// equal to Metadata.
|
||||
AddAnnotation(key, value string) error
|
||||
|
||||
// AddAnnotationWithLevel sets annotation according to key-value pair with additional intended audit level.
|
||||
// An Annotation gets logged into audit event if the request's audit level is greater or equal to the
|
||||
// intended audit level.
|
||||
AddAnnotationWithLevel(key, value string, level auditinternal.Level) error
|
||||
|
||||
// GetReinvocationContext tracks the admission request information relevant to the re-invocation policy.
|
||||
GetReinvocationContext() ReinvocationContext
|
||||
}
|
||||
|
||||
// ObjectInterfaces is an interface used by AdmissionController to get object interfaces
|
||||
// such as Converter or Defaulter. These interfaces are normally coming from Request Scope
|
||||
// to handle special cases like CRDs.
|
||||
type ObjectInterfaces interface {
|
||||
// GetObjectCreater is the ObjectCreator appropriate for the requested object.
|
||||
GetObjectCreater() runtime.ObjectCreater
|
||||
// GetObjectTyper is the ObjectTyper appropriate for the requested object.
|
||||
GetObjectTyper() runtime.ObjectTyper
|
||||
// GetObjectDefaulter is the ObjectDefaulter appropriate for the requested object.
|
||||
GetObjectDefaulter() runtime.ObjectDefaulter
|
||||
// GetObjectConvertor is the ObjectConvertor appropriate for the requested object.
|
||||
GetObjectConvertor() runtime.ObjectConvertor
|
||||
// GetEquivalentResourceMapper is the EquivalentResourceMapper appropriate for finding equivalent resources and expected kind for the requested object.
|
||||
GetEquivalentResourceMapper() runtime.EquivalentResourceMapper
|
||||
}
|
||||
|
||||
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
|
||||
type privateAnnotationsGetter interface {
|
||||
getAnnotations() map[string]string
|
||||
getAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||
}
|
||||
|
||||
// AnnotationsGetter allows users to get annotations from Attributes. An alternate Attribute should implement
|
||||
// this interface.
|
||||
type AnnotationsGetter interface {
|
||||
GetAnnotations() map[string]string
|
||||
GetAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||
}
|
||||
|
||||
// ReinvocationContext provides access to the admission related state required to implement the re-invocation policy.
|
||||
type ReinvocationContext interface {
|
||||
// IsReinvoke returns true if the current admission check is a re-invocation.
|
||||
IsReinvoke() bool
|
||||
// SetIsReinvoke sets the current admission check as a re-invocation.
|
||||
SetIsReinvoke()
|
||||
// ShouldReinvoke returns true if any plugin has requested a re-invocation.
|
||||
ShouldReinvoke() bool
|
||||
// SetShouldReinvoke signals that a re-invocation is desired.
|
||||
SetShouldReinvoke()
|
||||
// AddValue set a value for a plugin name, possibly overriding a previous value.
|
||||
SetValue(plugin string, v interface{})
|
||||
// Value reads a value for a webhook.
|
||||
Value(plugin string) interface{}
|
||||
}
|
||||
|
||||
// Interface is an abstract, pluggable interface for Admission Control decisions.
|
||||
@@ -83,8 +129,9 @@ type Interface interface {
|
||||
type MutationInterface interface {
|
||||
Interface
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
Admit(a Attributes) (err error)
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
|
||||
}
|
||||
|
||||
// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
|
||||
@@ -92,7 +139,8 @@ type ValidationInterface interface {
|
||||
Interface
|
||||
|
||||
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
|
||||
Validate(a Attributes) (err error)
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
|
||||
}
|
||||
|
||||
// Operation is the type of resource operation being checked for admission control
|
||||
|
||||
105
vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
105
vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
@@ -17,23 +17,36 @@ limitations under the License.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
// WebhookRejectionErrorType defines different error types that happen in a webhook rejection.
|
||||
type WebhookRejectionErrorType string
|
||||
|
||||
const (
|
||||
namespace = "apiserver"
|
||||
subsystem = "admission"
|
||||
|
||||
// WebhookRejectionCallingWebhookError identifies a calling webhook error which causes
|
||||
// a webhook admission to reject a request
|
||||
WebhookRejectionCallingWebhookError WebhookRejectionErrorType = "calling_webhook_error"
|
||||
// WebhookRejectionAPIServerInternalError identifies an apiserver internal error which
|
||||
// causes a webhook admission to reject a request
|
||||
WebhookRejectionAPIServerInternalError WebhookRejectionErrorType = "apiserver_internal_error"
|
||||
// WebhookRejectionNoError identifies a webhook properly rejected a request
|
||||
WebhookRejectionNoError WebhookRejectionErrorType = "no_error"
|
||||
)
|
||||
|
||||
var (
|
||||
// Use buckets ranging from 25 ms to ~2.5 seconds.
|
||||
latencyBuckets = prometheus.ExponentialBuckets(25000, 2.5, 5)
|
||||
// Use buckets ranging from 5 ms to 2.5 seconds (admission webhooks timeout at 30 seconds by default).
|
||||
latencyBuckets = []float64{0.005, 0.025, 0.1, 0.5, 2.5}
|
||||
latencySummaryMaxAge = 5 * time.Hour
|
||||
|
||||
// Metrics provides access to all admission metrics.
|
||||
@@ -75,36 +88,37 @@ type pluginHandlerWithMetrics struct {
|
||||
}
|
||||
|
||||
// Admit performs a mutating admission control check and emit metrics.
|
||||
func (p pluginHandlerWithMetrics) Admit(a admission.Attributes) error {
|
||||
func (p pluginHandlerWithMetrics) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
mutatingHandler, ok := p.Interface.(admission.MutationInterface)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err := mutatingHandler.Admit(a)
|
||||
err := mutatingHandler.Admit(ctx, a, o)
|
||||
p.observer(time.Since(start), err != nil, a, stepAdmit, p.extraLabels...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate performs a non-mutating admission control check and emits metrics.
|
||||
func (p pluginHandlerWithMetrics) Validate(a admission.Attributes) error {
|
||||
func (p pluginHandlerWithMetrics) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
validatingHandler, ok := p.Interface.(admission.ValidationInterface)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err := validatingHandler.Validate(a)
|
||||
err := validatingHandler.Validate(ctx, a, o)
|
||||
p.observer(time.Since(start), err != nil, a, stepValidate, p.extraLabels...)
|
||||
return err
|
||||
}
|
||||
|
||||
// AdmissionMetrics instruments admission with prometheus metrics.
|
||||
type AdmissionMetrics struct {
|
||||
step *metricSet
|
||||
controller *metricSet
|
||||
webhook *metricSet
|
||||
step *metricSet
|
||||
controller *metricSet
|
||||
webhook *metricSet
|
||||
webhookRejection *metrics.CounterVec
|
||||
}
|
||||
|
||||
// newAdmissionMetrics create a new AdmissionMetrics, configured with default metric names.
|
||||
@@ -125,10 +139,21 @@ func newAdmissionMetrics() *AdmissionMetrics {
|
||||
[]string{"name", "type", "operation", "rejected"},
|
||||
"Admission webhook %s, identified by name and broken out for each operation and API resource and type (validate or admit).", false)
|
||||
|
||||
webhookRejection := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_rejection_count",
|
||||
Help: "Admission webhook rejection count, identified by name and broken out for each admission type (validating or admit) and operation. Additional labels specify an error type (calling_webhook_error or apiserver_internal_error if an error occurred; no_error otherwise) and optionally a non-zero rejection code if the webhook rejects the request with an HTTP status code (honored by the apiserver when the code is greater or equal to 400). Codes greater than 600 are truncated to 600, to keep the metrics cardinality bounded.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type", "operation", "error_type", "rejection_code"})
|
||||
|
||||
step.mustRegister()
|
||||
controller.mustRegister()
|
||||
webhook.mustRegister()
|
||||
return &AdmissionMetrics{step: step, controller: controller, webhook: webhook}
|
||||
legacyregistry.MustRegister(webhookRejection)
|
||||
return &AdmissionMetrics{step: step, controller: controller, webhook: webhook, webhookRejection: webhookRejection}
|
||||
}
|
||||
|
||||
func (m *AdmissionMetrics) reset() {
|
||||
@@ -152,34 +177,46 @@ func (m *AdmissionMetrics) ObserveWebhook(elapsed time.Duration, rejected bool,
|
||||
m.webhook.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))...)
|
||||
}
|
||||
|
||||
// ObserveWebhookRejection records admission related metrics for an admission webhook rejection.
|
||||
func (m *AdmissionMetrics) ObserveWebhookRejection(name, stepType, operation string, errorType WebhookRejectionErrorType, rejectionCode int) {
|
||||
// We truncate codes greater than 600 to keep the cardinality bounded.
|
||||
// This should be rarely done by a malfunctioning webhook server.
|
||||
if rejectionCode > 600 {
|
||||
rejectionCode = 600
|
||||
}
|
||||
m.webhookRejection.WithLabelValues(name, stepType, operation, string(errorType), strconv.Itoa(rejectionCode)).Inc()
|
||||
}
|
||||
|
||||
type metricSet struct {
|
||||
latencies *prometheus.HistogramVec
|
||||
latenciesSummary *prometheus.SummaryVec
|
||||
latencies *metrics.HistogramVec
|
||||
latenciesSummary *metrics.SummaryVec
|
||||
}
|
||||
|
||||
func newMetricSet(name string, labels []string, helpTemplate string, hasSummary bool) *metricSet {
|
||||
var summary *prometheus.SummaryVec
|
||||
var summary *metrics.SummaryVec
|
||||
if hasSummary {
|
||||
summary = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: fmt.Sprintf("%s_admission_latencies_seconds_summary", name),
|
||||
Help: fmt.Sprintf(helpTemplate, "latency summary"),
|
||||
MaxAge: latencySummaryMaxAge,
|
||||
summary = metrics.NewSummaryVec(
|
||||
&metrics.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: fmt.Sprintf("%s_admission_duration_seconds_summary", name),
|
||||
Help: fmt.Sprintf(helpTemplate, "latency summary in seconds"),
|
||||
MaxAge: latencySummaryMaxAge,
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
labels,
|
||||
)
|
||||
}
|
||||
|
||||
return &metricSet{
|
||||
latencies: prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: fmt.Sprintf("%s_admission_latencies_seconds", name),
|
||||
Help: fmt.Sprintf(helpTemplate, "latency histogram"),
|
||||
Buckets: latencyBuckets,
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: fmt.Sprintf("%s_admission_duration_seconds", name),
|
||||
Help: fmt.Sprintf(helpTemplate, "latency histogram in seconds"),
|
||||
Buckets: latencyBuckets,
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
labels,
|
||||
),
|
||||
@@ -190,9 +227,9 @@ func newMetricSet(name string, labels []string, helpTemplate string, hasSummary
|
||||
|
||||
// MustRegister registers all the prometheus metrics in the metricSet.
|
||||
func (m *metricSet) mustRegister() {
|
||||
prometheus.MustRegister(m.latencies)
|
||||
legacyregistry.MustRegister(m.latencies)
|
||||
if m.latenciesSummary != nil {
|
||||
prometheus.MustRegister(m.latenciesSummary)
|
||||
legacyregistry.MustRegister(m.latenciesSummary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,9 +243,9 @@ func (m *metricSet) reset() {
|
||||
|
||||
// Observe records an observed admission event to all metrics in the metricSet.
|
||||
func (m *metricSet) observe(elapsed time.Duration, labels ...string) {
|
||||
elapsedMicroseconds := float64(elapsed / time.Microsecond)
|
||||
m.latencies.WithLabelValues(labels...).Observe(elapsedMicroseconds)
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
m.latencies.WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
if m.latenciesSummary != nil {
|
||||
m.latenciesSummary.WithLabelValues(labels...).Observe(elapsedMicroseconds)
|
||||
m.latenciesSummary.WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
369
vendor/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go
generated
vendored
369
vendor/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go
generated
vendored
@@ -1,369 +0,0 @@
|
||||
/*
|
||||
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 initialization
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name of admission plug-in
|
||||
PluginName = "Initializers"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewInitializer(), nil
|
||||
})
|
||||
}
|
||||
|
||||
type initializerOptions struct {
|
||||
Initializers []string
|
||||
}
|
||||
|
||||
// InitializationConfig specifies initialization config
|
||||
type InitializationConfig interface {
|
||||
Run(stopCh <-chan struct{})
|
||||
Initializers() (*v1alpha1.InitializerConfiguration, error)
|
||||
}
|
||||
|
||||
type initializer struct {
|
||||
config InitializationConfig
|
||||
authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
// NewInitializer creates a new initializer plugin which assigns newly created resources initializers
|
||||
// based on configuration loaded from the admission API group.
|
||||
// FUTURE: this may be moved to the storage layer of the apiserver, but for now this is an alpha feature
|
||||
// that can be disabled.
|
||||
func NewInitializer() admission.Interface {
|
||||
return &initializer{}
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (i *initializer) ValidateInitialization() error {
|
||||
if i.config == nil {
|
||||
return fmt.Errorf("the Initializer admission plugin requires a Kubernetes client to be provided")
|
||||
}
|
||||
if i.authorizer == nil {
|
||||
return fmt.Errorf("the Initializer admission plugin requires an authorizer to be provided")
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
|
||||
if err := utilfeature.DefaultFeatureGate.Set(string(features.Initializers) + "=true"); err != nil {
|
||||
klog.Errorf("error enabling Initializers feature as part of admission plugin setup: %v", err)
|
||||
} else {
|
||||
klog.Infof("enabled Initializers feature as part of admission plugin setup")
|
||||
}
|
||||
}
|
||||
|
||||
i.config.Run(wait.NeverStop)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
|
||||
func (i *initializer) SetExternalKubeClientSet(client clientset.Interface) {
|
||||
i.config = configuration.NewInitializerConfigurationManager(client.AdmissionregistrationV1alpha1().InitializerConfigurations())
|
||||
}
|
||||
|
||||
// SetAuthorizer implements the WantsAuthorizer interface.
|
||||
func (i *initializer) SetAuthorizer(a authorizer.Authorizer) {
|
||||
i.authorizer = a
|
||||
}
|
||||
|
||||
var initializerFieldPath = field.NewPath("metadata", "initializers")
|
||||
|
||||
// readConfig holds requests instead of failing them if the server is not yet initialized
|
||||
// or is unresponsive. It formats the returned error for client use if necessary.
|
||||
func (i *initializer) readConfig(a admission.Attributes) (*v1alpha1.InitializerConfiguration, error) {
|
||||
// read initializers from config
|
||||
config, err := i.config.Initializers()
|
||||
if err == nil {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// if initializer configuration is disabled, fail open
|
||||
if err == configuration.ErrDisabled {
|
||||
return &v1alpha1.InitializerConfiguration{}, nil
|
||||
}
|
||||
|
||||
e := errors.NewServerTimeout(a.GetResource().GroupResource(), "create", 1)
|
||||
if err == configuration.ErrNotReady {
|
||||
e.ErrStatus.Message = fmt.Sprintf("Waiting for initialization configuration to load: %v", err)
|
||||
e.ErrStatus.Reason = "LoadingConfiguration"
|
||||
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: "InitializerConfigurationPending",
|
||||
Message: "The server is waiting for the initializer configuration to be loaded.",
|
||||
})
|
||||
} else {
|
||||
e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the initializer configuration: %v", err)
|
||||
e.ErrStatus.Reason = "LoadingConfiguration"
|
||||
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: "InitializerConfigurationFailure",
|
||||
Message: "An error has occurred while refreshing the initializer configuration, no resources can be created until a refresh succeeds.",
|
||||
})
|
||||
}
|
||||
return nil, e
|
||||
}
|
||||
|
||||
// Admit checks for create requests to add initializers, or update request to enforce invariants.
|
||||
// The admission controller fails open if the object doesn't have ObjectMeta (can't be initialized).
|
||||
// A client with sufficient permission ("initialize" verb on resource) can specify its own initializers
|
||||
// or an empty initializers struct (which bypasses initialization). Only clients with the initialize verb
|
||||
// can update objects that have not completed initialization. Sub resources can still be modified on
|
||||
// resources that are undergoing initialization.
|
||||
// TODO: once this logic is ready for beta, move it into the REST storage layer.
|
||||
func (i *initializer) Admit(a admission.Attributes) (err error) {
|
||||
switch a.GetOperation() {
|
||||
case admission.Create, admission.Update:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: should sub-resource action should be denied until the object is initialized?
|
||||
if len(a.GetSubresource()) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch a.GetOperation() {
|
||||
case admission.Create:
|
||||
accessor, err := meta.Accessor(a.GetObject())
|
||||
if err != nil {
|
||||
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
|
||||
// via our API that don't have ObjectMeta
|
||||
return nil
|
||||
}
|
||||
existing := accessor.GetInitializers()
|
||||
if existing != nil {
|
||||
klog.V(5).Infof("Admin bypassing initialization for %s", a.GetResource())
|
||||
|
||||
// it must be possible for some users to bypass initialization - for now, check the initialize operation
|
||||
if err := i.canInitialize(a, "create with initializers denied"); err != nil {
|
||||
return err
|
||||
}
|
||||
// allow administrators to bypass initialization by setting an empty initializers struct
|
||||
if len(existing.Pending) == 0 && existing.Result == nil {
|
||||
accessor.SetInitializers(nil)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
klog.V(5).Infof("Checking initialization for %s", a.GetResource())
|
||||
|
||||
config, err := i.readConfig(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mirror pods are exempt from initialization because they are created and initialized
|
||||
// on the Kubelet before they appear in the API.
|
||||
// TODO: once this moves to REST storage layer, this becomes a pod specific concern
|
||||
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Pod").GroupKind() {
|
||||
accessor, err := meta.Accessor(a.GetObject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
annotations := accessor.GetAnnotations()
|
||||
if _, isMirror := annotations[v1.MirrorPodAnnotationKey]; isMirror {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
names := findInitializers(config, a.GetResource())
|
||||
if len(names) == 0 {
|
||||
klog.V(5).Infof("No initializers needed")
|
||||
return nil
|
||||
}
|
||||
|
||||
klog.V(5).Infof("Found initializers for %s: %v", a.GetResource(), names)
|
||||
accessor.SetInitializers(newInitializers(names))
|
||||
}
|
||||
|
||||
case admission.Update:
|
||||
accessor, err := meta.Accessor(a.GetObject())
|
||||
if err != nil {
|
||||
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
|
||||
// via our API that don't have ObjectMeta
|
||||
return nil
|
||||
}
|
||||
updated := accessor.GetInitializers()
|
||||
|
||||
// controllers deployed with an empty initializers.pending have their initializers set to nil
|
||||
// but should be able to update without changing their manifest
|
||||
if updated != nil && len(updated.Pending) == 0 && updated.Result == nil {
|
||||
accessor.SetInitializers(nil)
|
||||
updated = nil
|
||||
}
|
||||
|
||||
existingAccessor, err := meta.Accessor(a.GetOldObject())
|
||||
if err != nil {
|
||||
// if the old object does not have an accessor, but the new one does, error out
|
||||
return fmt.Errorf("initialized resources must be able to set initializers (%T): %v", a.GetOldObject(), err)
|
||||
}
|
||||
existing := existingAccessor.GetInitializers()
|
||||
|
||||
// updates on initialized resources are allowed
|
||||
if updated == nil && existing == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
klog.V(5).Infof("Modifying uninitialized resource %s", a.GetResource())
|
||||
|
||||
// because we are called before validation, we need to ensure the update transition is valid.
|
||||
if errs := validation.ValidateInitializersUpdate(updated, existing, initializerFieldPath); len(errs) > 0 {
|
||||
return errors.NewInvalid(a.GetKind().GroupKind(), a.GetName(), errs)
|
||||
}
|
||||
|
||||
// caller must have the ability to mutate un-initialized resources
|
||||
if err := i.canInitialize(a, "update to uninitialized resource denied"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: restrict initialization list changes to specific clients?
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *initializer) canInitialize(a admission.Attributes, message string) error {
|
||||
// caller must have the ability to mutate un-initialized resources
|
||||
decision, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{
|
||||
Name: a.GetName(),
|
||||
ResourceRequest: true,
|
||||
User: a.GetUserInfo(),
|
||||
Verb: "initialize",
|
||||
Namespace: a.GetNamespace(),
|
||||
APIGroup: a.GetResource().Group,
|
||||
APIVersion: a.GetResource().Version,
|
||||
Resource: a.GetResource().Resource,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if decision != authorizer.DecisionAllow {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("%s: %s", message, reason))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
func (i *initializer) Handles(op admission.Operation) bool {
|
||||
return op == admission.Create || op == admission.Update
|
||||
}
|
||||
|
||||
// newInitializers populates an Initializers struct.
|
||||
func newInitializers(names []string) *metav1.Initializers {
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
var init []metav1.Initializer
|
||||
for _, name := range names {
|
||||
init = append(init, metav1.Initializer{Name: name})
|
||||
}
|
||||
return &metav1.Initializers{
|
||||
Pending: init,
|
||||
}
|
||||
}
|
||||
|
||||
// findInitializers returns the list of initializer names that apply to a config. It returns an empty list
|
||||
// if no initializers apply.
|
||||
func findInitializers(initializers *v1alpha1.InitializerConfiguration, gvr schema.GroupVersionResource) []string {
|
||||
var names []string
|
||||
for _, init := range initializers.Initializers {
|
||||
if !matchRule(init.Rules, gvr) {
|
||||
continue
|
||||
}
|
||||
names = append(names, init.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// matchRule returns true if any rule matches the provided group version resource.
|
||||
func matchRule(rules []v1alpha1.Rule, gvr schema.GroupVersionResource) bool {
|
||||
for _, rule := range rules {
|
||||
if !hasGroup(rule.APIGroups, gvr.Group) {
|
||||
return false
|
||||
}
|
||||
if !hasVersion(rule.APIVersions, gvr.Version) {
|
||||
return false
|
||||
}
|
||||
if !hasResource(rule.Resources, gvr.Resource) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(rules) > 0
|
||||
}
|
||||
|
||||
func hasGroup(groups []string, group string) bool {
|
||||
if groups[0] == "*" {
|
||||
return true
|
||||
}
|
||||
for _, g := range groups {
|
||||
if g == group {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasVersion(versions []string, version string) bool {
|
||||
if versions[0] == "*" {
|
||||
return true
|
||||
}
|
||||
for _, v := range versions {
|
||||
if v == version {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasResource(resources []string, resource string) bool {
|
||||
if resources[0] == "*" || resources[0] == "*/*" {
|
||||
return true
|
||||
}
|
||||
for _, r := range resources {
|
||||
if strings.Contains(r, "/") {
|
||||
continue
|
||||
}
|
||||
if r == resource {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
5
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
5
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
@@ -17,13 +17,14 @@ limitations under the License.
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -73,7 +74,7 @@ var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (l *Lifecycle) Admit(a admission.Attributes) error {
|
||||
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
|
||||
297
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
297
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
Copyright 2019 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 webhook
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// WebhookAccessor provides a common interface to both mutating and validating webhook types.
|
||||
type WebhookAccessor interface {
|
||||
// GetUID gets a string that uniquely identifies the webhook.
|
||||
GetUID() string
|
||||
|
||||
// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
|
||||
GetConfigurationName() string
|
||||
|
||||
// GetRESTClient gets the webhook client
|
||||
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
||||
// GetParsedNamespaceSelector gets the webhook NamespaceSelector field.
|
||||
GetParsedNamespaceSelector() (labels.Selector, error)
|
||||
// GetParsedObjectSelector gets the webhook ObjectSelector field.
|
||||
GetParsedObjectSelector() (labels.Selector, error)
|
||||
|
||||
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
||||
// configuration and does not provide a globally unique identity, if a unique identity is
|
||||
// needed, use GetUID.
|
||||
GetName() string
|
||||
// GetClientConfig gets the webhook ClientConfig field.
|
||||
GetClientConfig() v1beta1.WebhookClientConfig
|
||||
// GetRules gets the webhook Rules field.
|
||||
GetRules() []v1beta1.RuleWithOperations
|
||||
// GetFailurePolicy gets the webhook FailurePolicy field.
|
||||
GetFailurePolicy() *v1beta1.FailurePolicyType
|
||||
// GetMatchPolicy gets the webhook MatchPolicy field.
|
||||
GetMatchPolicy() *v1beta1.MatchPolicyType
|
||||
// GetNamespaceSelector gets the webhook NamespaceSelector field.
|
||||
GetNamespaceSelector() *metav1.LabelSelector
|
||||
// GetObjectSelector gets the webhook ObjectSelector field.
|
||||
GetObjectSelector() *metav1.LabelSelector
|
||||
// GetSideEffects gets the webhook SideEffects field.
|
||||
GetSideEffects() *v1beta1.SideEffectClass
|
||||
// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
|
||||
GetTimeoutSeconds() *int32
|
||||
// GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field.
|
||||
GetAdmissionReviewVersions() []string
|
||||
|
||||
// GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false.
|
||||
GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool)
|
||||
// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
|
||||
GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool)
|
||||
}
|
||||
|
||||
// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook.
|
||||
func NewMutatingWebhookAccessor(uid, configurationName string, h *v1beta1.MutatingWebhook) WebhookAccessor {
|
||||
return &mutatingWebhookAccessor{uid: uid, configurationName: configurationName, MutatingWebhook: h}
|
||||
}
|
||||
|
||||
type mutatingWebhookAccessor struct {
|
||||
*v1beta1.MutatingWebhook
|
||||
uid string
|
||||
configurationName string
|
||||
|
||||
initObjectSelector sync.Once
|
||||
objectSelector labels.Selector
|
||||
objectSelectorErr error
|
||||
|
||||
initNamespaceSelector sync.Once
|
||||
namespaceSelector labels.Selector
|
||||
namespaceSelectorErr error
|
||||
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetUID() string {
|
||||
return m.uid
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetConfigurationName() string {
|
||||
return m.configurationName
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
|
||||
m.initClient.Do(func() {
|
||||
m.client, m.clientErr = clientManager.HookClient(hookClientConfigForWebhook(m))
|
||||
})
|
||||
return m.client, m.clientErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
m.initNamespaceSelector.Do(func() {
|
||||
m.namespaceSelector, m.namespaceSelectorErr = metav1.LabelSelectorAsSelector(m.NamespaceSelector)
|
||||
})
|
||||
return m.namespaceSelector, m.namespaceSelectorErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
m.initObjectSelector.Do(func() {
|
||||
m.objectSelector, m.objectSelectorErr = metav1.LabelSelectorAsSelector(m.ObjectSelector)
|
||||
})
|
||||
return m.objectSelector, m.objectSelectorErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetName() string {
|
||||
return m.Name
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
|
||||
return m.ClientConfig
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
|
||||
return m.Rules
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
|
||||
return m.FailurePolicy
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
|
||||
return m.MatchPolicy
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
|
||||
return m.NamespaceSelector
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
|
||||
return m.ObjectSelector
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
|
||||
return m.SideEffects
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetTimeoutSeconds() *int32 {
|
||||
return m.TimeoutSeconds
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return m.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
|
||||
return m.MutatingWebhook, true
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook.
|
||||
func NewValidatingWebhookAccessor(uid, configurationName string, h *v1beta1.ValidatingWebhook) WebhookAccessor {
|
||||
return &validatingWebhookAccessor{uid: uid, configurationName: configurationName, ValidatingWebhook: h}
|
||||
}
|
||||
|
||||
type validatingWebhookAccessor struct {
|
||||
*v1beta1.ValidatingWebhook
|
||||
uid string
|
||||
configurationName string
|
||||
|
||||
initObjectSelector sync.Once
|
||||
objectSelector labels.Selector
|
||||
objectSelectorErr error
|
||||
|
||||
initNamespaceSelector sync.Once
|
||||
namespaceSelector labels.Selector
|
||||
namespaceSelectorErr error
|
||||
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetUID() string {
|
||||
return v.uid
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetConfigurationName() string {
|
||||
return v.configurationName
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
|
||||
v.initClient.Do(func() {
|
||||
v.client, v.clientErr = clientManager.HookClient(hookClientConfigForWebhook(v))
|
||||
})
|
||||
return v.client, v.clientErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
v.initNamespaceSelector.Do(func() {
|
||||
v.namespaceSelector, v.namespaceSelectorErr = metav1.LabelSelectorAsSelector(v.NamespaceSelector)
|
||||
})
|
||||
return v.namespaceSelector, v.namespaceSelectorErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
v.initObjectSelector.Do(func() {
|
||||
v.objectSelector, v.objectSelectorErr = metav1.LabelSelectorAsSelector(v.ObjectSelector)
|
||||
})
|
||||
return v.objectSelector, v.objectSelectorErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
|
||||
return v.ClientConfig
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
|
||||
return v.Rules
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
|
||||
return v.FailurePolicy
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
|
||||
return v.MatchPolicy
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
|
||||
return v.NamespaceSelector
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
|
||||
return v.ObjectSelector
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
|
||||
return v.SideEffects
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetTimeoutSeconds() *int32 {
|
||||
return v.TimeoutSeconds
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return v.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
|
||||
return v.ValidatingWebhook, true
|
||||
}
|
||||
|
||||
// hookClientConfigForWebhook construct a webhookutil.ClientConfig using a WebhookAccessor to access
|
||||
// v1beta1.MutatingWebhook and v1beta1.ValidatingWebhook API objects. webhookutil.ClientConfig is used
|
||||
// to create a HookClient and the purpose of the config struct is to share that with other packages
|
||||
// that need to create a HookClient.
|
||||
func hookClientConfigForWebhook(w WebhookAccessor) webhookutil.ClientConfig {
|
||||
ret := webhookutil.ClientConfig{Name: w.GetName(), CABundle: w.GetClientConfig().CABundle}
|
||||
if w.GetClientConfig().URL != nil {
|
||||
ret.URL = *w.GetClientConfig().URL
|
||||
}
|
||||
if w.GetClientConfig().Service != nil {
|
||||
ret.Service = &webhookutil.ClientConfigService{
|
||||
Name: w.GetClientConfig().Service.Name,
|
||||
Namespace: w.GetClientConfig().Service.Namespace,
|
||||
}
|
||||
if w.GetClientConfig().Service.Port != nil {
|
||||
ret.Service.Port = *w.GetClientConfig().Service.Port
|
||||
} else {
|
||||
ret.Service.Port = 443
|
||||
}
|
||||
if w.GetClientConfig().Service.Path != nil {
|
||||
ret.Service.Path = *w.GetClientConfig().Service.Path
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
10
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go
generated
vendored
@@ -18,6 +18,7 @@ package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -32,6 +33,15 @@ func ToStatusErr(webhookName string, result *metav1.Status) *apierrors.StatusErr
|
||||
result = &metav1.Status{Status: metav1.StatusFailure}
|
||||
}
|
||||
|
||||
// Make sure we don't return < 400 status codes along with a rejection
|
||||
if result.Code < http.StatusBadRequest {
|
||||
result.Code = http.StatusBadRequest
|
||||
}
|
||||
// Make sure we don't return "" or "Success" status along with a rejection
|
||||
if result.Status == "" || result.Status == metav1.StatusSuccess {
|
||||
result.Status = metav1.StatusFailure
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(result.Message) > 0:
|
||||
result.Message = fmt.Sprintf("%s: %s", deniedBy, result.Message)
|
||||
|
||||
83
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/conversion.go
generated
vendored
83
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/conversion.go
generated
vendored
@@ -17,29 +17,23 @@ limitations under the License.
|
||||
package generic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// convertor converts objects to the desired version.
|
||||
type convertor struct {
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// ConvertToGVK converts object to the desired gvk.
|
||||
func (c *convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) {
|
||||
func ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind, o admission.ObjectInterfaces) (runtime.Object, error) {
|
||||
// Unlike other resources, custom resources do not have internal version, so
|
||||
// if obj is a custom resource, it should not need conversion.
|
||||
if obj.GetObjectKind().GroupVersionKind() == gvk {
|
||||
return obj, nil
|
||||
}
|
||||
out, err := c.Scheme.New(gvk)
|
||||
out, err := o.GetObjectCreater().New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.Scheme.Convert(obj, out, nil)
|
||||
err = o.GetObjectConvertor().Convert(obj, out, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,10 +42,71 @@ func (c *convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Validate checks if the conversion has a scheme.
|
||||
func (c *convertor) Validate() error {
|
||||
if c.Scheme == nil {
|
||||
return fmt.Errorf("the convertor requires a scheme")
|
||||
// NewVersionedAttributes returns versioned attributes with the old and new object (if non-nil) converted to the requested kind
|
||||
func NewVersionedAttributes(attr admission.Attributes, gvk schema.GroupVersionKind, o admission.ObjectInterfaces) (*VersionedAttributes, error) {
|
||||
// convert the old and new objects to the requested version
|
||||
versionedAttr := &VersionedAttributes{
|
||||
Attributes: attr,
|
||||
VersionedKind: gvk,
|
||||
}
|
||||
if oldObj := attr.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedOldObject = out
|
||||
}
|
||||
if obj := attr.GetObject(); obj != nil {
|
||||
out, err := ConvertToGVK(obj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedObject = out
|
||||
}
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
// ConvertVersionedAttributes converts VersionedObject and VersionedOldObject to the specified kind, if needed.
|
||||
// If attr.VersionedKind already matches the requested kind, no conversion is performed.
|
||||
// If conversion is required:
|
||||
// * attr.VersionedObject is used as the source for the new object if Dirty=true (and is round-tripped through attr.Attributes.Object, clearing Dirty in the process)
|
||||
// * attr.Attributes.Object is used as the source for the new object if Dirty=false
|
||||
// * attr.Attributes.OldObject is used as the source for the old object
|
||||
func ConvertVersionedAttributes(attr *VersionedAttributes, gvk schema.GroupVersionKind, o admission.ObjectInterfaces) error {
|
||||
// we already have the desired kind, we're done
|
||||
if attr.VersionedKind == gvk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the original old object to the desired GVK
|
||||
if oldObj := attr.Attributes.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedOldObject = out
|
||||
}
|
||||
|
||||
if attr.VersionedObject != nil {
|
||||
// convert the existing versioned object to internal
|
||||
if attr.Dirty {
|
||||
err := o.GetObjectConvertor().Convert(attr.VersionedObject, attr.Attributes.GetObject(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// and back to external
|
||||
out, err := ConvertToGVK(attr.Attributes.GetObject(), gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedObject = out
|
||||
}
|
||||
|
||||
// Remember we converted to this version
|
||||
attr.VersionedKind = gvk
|
||||
attr.Dirty = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
40
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go
generated
vendored
40
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go
generated
vendored
@@ -19,27 +19,57 @@ package generic
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
)
|
||||
|
||||
// Source can list dynamic webhook plugins.
|
||||
type Source interface {
|
||||
Webhooks() []v1beta1.Webhook
|
||||
Webhooks() []webhook.WebhookAccessor
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
// VersionedAttributes is a wrapper around the original admission attributes, adding versioned
|
||||
// variants of the object and old object.
|
||||
type VersionedAttributes struct {
|
||||
// Attributes holds the original admission attributes
|
||||
admission.Attributes
|
||||
// VersionedOldObject holds Attributes.OldObject (if non-nil), converted to VersionedKind.
|
||||
// It must never be mutated.
|
||||
VersionedOldObject runtime.Object
|
||||
VersionedObject runtime.Object
|
||||
// VersionedObject holds Attributes.Object (if non-nil), converted to VersionedKind.
|
||||
// If mutated, Dirty must be set to true by the mutator.
|
||||
VersionedObject runtime.Object
|
||||
// VersionedKind holds the fully qualified kind
|
||||
VersionedKind schema.GroupVersionKind
|
||||
// Dirty indicates VersionedObject has been modified since being converted from Attributes.Object
|
||||
Dirty bool
|
||||
}
|
||||
|
||||
// GetObject overrides the Attributes.GetObject()
|
||||
func (v *VersionedAttributes) GetObject() runtime.Object {
|
||||
if v.VersionedObject != nil {
|
||||
return v.VersionedObject
|
||||
}
|
||||
return v.Attributes.GetObject()
|
||||
}
|
||||
|
||||
// WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for,
|
||||
// and the kind that should be sent to the webhook.
|
||||
type WebhookInvocation struct {
|
||||
Webhook webhook.WebhookAccessor
|
||||
Resource schema.GroupVersionResource
|
||||
Subresource string
|
||||
Kind schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// Dispatcher dispatches webhook call to a list of webhooks with admission attributes as argument.
|
||||
type Dispatcher interface {
|
||||
// Dispatch a request to the webhooks using the given webhooks. A non-nil error means the request is rejected.
|
||||
Dispatch(ctx context.Context, a *VersionedAttributes, hooks []*v1beta1.Webhook) error
|
||||
// Dispatch a request to the webhooks. Dispatcher may choose not to
|
||||
// call a hook, either because the rules of the hook does not match, or
|
||||
// the namespaceSelector or the objectSelector of the hook does not
|
||||
// match. A non-nil error means the request is rejected.
|
||||
Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error
|
||||
}
|
||||
|
||||
154
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
154
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
@@ -21,16 +21,19 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/object"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/informers"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
)
|
||||
@@ -42,9 +45,9 @@ type Webhook struct {
|
||||
sourceFactory sourceFactory
|
||||
|
||||
hookSource Source
|
||||
clientManager *webhook.ClientManager
|
||||
convertor *convertor
|
||||
clientManager *webhookutil.ClientManager
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
dispatcher Dispatcher
|
||||
}
|
||||
|
||||
@@ -54,7 +57,7 @@ var (
|
||||
)
|
||||
|
||||
type sourceFactory func(f informers.SharedInformerFactory) Source
|
||||
type dispatcherFactory func(cm *webhook.ClientManager) Dispatcher
|
||||
type dispatcherFactory func(cm *webhookutil.ClientManager) Dispatcher
|
||||
|
||||
// NewWebhook creates a new generic admission webhook.
|
||||
func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) {
|
||||
@@ -63,24 +66,31 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cm, err := webhook.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme)
|
||||
cm, err := webhookutil.NewClientManager(
|
||||
[]schema.GroupVersion{
|
||||
admissionv1beta1.SchemeGroupVersion,
|
||||
admissionv1.SchemeGroupVersion,
|
||||
},
|
||||
admissionv1beta1.AddToScheme,
|
||||
admissionv1.AddToScheme,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
|
||||
authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set defaults which may be overridden later.
|
||||
cm.SetAuthenticationInfoResolver(authInfoResolver)
|
||||
cm.SetServiceResolver(webhook.NewDefaultServiceResolver())
|
||||
cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver())
|
||||
|
||||
return &Webhook{
|
||||
Handler: handler,
|
||||
sourceFactory: sourceFactory,
|
||||
clientManager: &cm,
|
||||
convertor: &convertor{},
|
||||
namespaceMatcher: &namespace.Matcher{},
|
||||
objectMatcher: &object.Matcher{},
|
||||
dispatcher: dispatcherFactory(&cm),
|
||||
}, nil
|
||||
}
|
||||
@@ -88,23 +98,16 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
|
||||
// SetAuthenticationInfoResolverWrapper sets the
|
||||
// AuthenticationInfoResolverWrapper.
|
||||
// TODO find a better way wire this, but keep this pull small for now.
|
||||
func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhook.AuthenticationInfoResolverWrapper) {
|
||||
func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhookutil.AuthenticationInfoResolverWrapper) {
|
||||
a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper)
|
||||
}
|
||||
|
||||
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
||||
// Passing a nil resolver does not have an effect, instead a default one will be used.
|
||||
func (a *Webhook) SetServiceResolver(sr webhook.ServiceResolver) {
|
||||
func (a *Webhook) SetServiceResolver(sr webhookutil.ServiceResolver) {
|
||||
a.clientManager.SetServiceResolver(sr)
|
||||
}
|
||||
|
||||
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
|
||||
func (a *Webhook) SetScheme(scheme *runtime.Scheme) {
|
||||
if scheme != nil {
|
||||
a.convertor.Scheme = scheme
|
||||
}
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface.
|
||||
// It sets external ClientSet for admission plugins that need it
|
||||
func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) {
|
||||
@@ -132,31 +135,83 @@ func (a *Webhook) ValidateInitialization() error {
|
||||
if err := a.clientManager.Validate(); err != nil {
|
||||
return fmt.Errorf("clientManager is not properly setup: %v", err)
|
||||
}
|
||||
if err := a.convertor.Validate(); err != nil {
|
||||
return fmt.Errorf("convertor is not properly setup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldCallHook makes a decision on whether to call the webhook or not by the attribute.
|
||||
func (a *Webhook) ShouldCallHook(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
var matches bool
|
||||
for _, r := range h.Rules {
|
||||
// ShouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called,
|
||||
// or an error if an error was encountered during evaluation.
|
||||
func (a *Webhook) ShouldCallHook(h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) {
|
||||
var err *apierrors.StatusError
|
||||
var invocation *WebhookInvocation
|
||||
for _, r := range h.GetRules() {
|
||||
m := rules.Matcher{Rule: r, Attr: attr}
|
||||
if m.Matches() {
|
||||
matches = true
|
||||
invocation = &WebhookInvocation{
|
||||
Webhook: h,
|
||||
Resource: attr.GetResource(),
|
||||
Subresource: attr.GetSubresource(),
|
||||
Kind: attr.GetKind(),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matches {
|
||||
return false, nil
|
||||
if invocation == nil && h.GetMatchPolicy() != nil && *h.GetMatchPolicy() == v1beta1.Equivalent {
|
||||
attrWithOverride := &attrWithResourceOverride{Attributes: attr}
|
||||
equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource())
|
||||
// honor earlier rules first
|
||||
OuterLoop:
|
||||
for _, r := range h.GetRules() {
|
||||
// see if the rule matches any of the equivalent resources
|
||||
for _, equivalent := range equivalents {
|
||||
if equivalent == attr.GetResource() {
|
||||
// exclude attr.GetResource(), which we already checked
|
||||
continue
|
||||
}
|
||||
attrWithOverride.resource = equivalent
|
||||
m := rules.Matcher{Rule: r, Attr: attrWithOverride}
|
||||
if m.Matches() {
|
||||
kind := o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
|
||||
if kind.Empty() {
|
||||
return nil, apierrors.NewInternalError(fmt.Errorf("unable to convert to %v: unknown kind", equivalent))
|
||||
}
|
||||
invocation = &WebhookInvocation{
|
||||
Webhook: h,
|
||||
Resource: equivalent,
|
||||
Subresource: attr.GetSubresource(),
|
||||
Kind: kind,
|
||||
}
|
||||
break OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return a.namespaceMatcher.MatchNamespaceSelector(h, attr)
|
||||
if invocation == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
matches, err := a.namespaceMatcher.MatchNamespaceSelector(h, attr)
|
||||
if !matches || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches, err = a.objectMatcher.MatchObjectSelector(h, attr)
|
||||
if !matches || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invocation, nil
|
||||
}
|
||||
|
||||
type attrWithResourceOverride struct {
|
||||
admission.Attributes
|
||||
resource schema.GroupVersionResource
|
||||
}
|
||||
|
||||
func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource }
|
||||
|
||||
// Dispatch is called by the downstream Validate or Admit methods.
|
||||
func (a *Webhook) Dispatch(attr admission.Attributes) error {
|
||||
func (a *Webhook) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
if rules.IsWebhookConfigurationResource(attr) {
|
||||
return nil
|
||||
}
|
||||
@@ -164,42 +219,5 @@ func (a *Webhook) Dispatch(attr admission.Attributes) error {
|
||||
return admission.NewForbidden(attr, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
hooks := a.hookSource.Webhooks()
|
||||
// TODO: Figure out if adding one second timeout make sense here.
|
||||
ctx := context.TODO()
|
||||
|
||||
var relevantHooks []*v1beta1.Webhook
|
||||
for i := range hooks {
|
||||
call, err := a.ShouldCallHook(&hooks[i], attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if call {
|
||||
relevantHooks = append(relevantHooks, &hooks[i])
|
||||
}
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
// no matching hooks
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the object to the external version before sending it to the webhook
|
||||
versionedAttr := VersionedAttributes{
|
||||
Attributes: attr,
|
||||
}
|
||||
if oldObj := attr.GetOldObject(); oldObj != nil {
|
||||
out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind())
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttr.VersionedOldObject = out
|
||||
}
|
||||
if obj := attr.GetObject(); obj != nil {
|
||||
out, err := a.convertor.ConvertToGVK(obj, attr.GetKind())
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttr.VersionedObject = out
|
||||
}
|
||||
return a.dispatcher.Dispatch(ctx, &versionedAttr, relevantHooks)
|
||||
return a.dispatcher.Dispatch(ctx, attr, o, hooks)
|
||||
}
|
||||
|
||||
369
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
369
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
@@ -24,124 +24,285 @@ import (
|
||||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/klog"
|
||||
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
// PatchAuditAnnotationPrefix is a prefix for persisting webhook patch in audit annotation.
|
||||
// Audit handler decides whether annotation with this prefix should be logged based on audit level.
|
||||
// Since mutating webhook patches the request body, audit level must be greater or equal to Request
|
||||
// for the annotation to be logged
|
||||
PatchAuditAnnotationPrefix = "patch.webhook.admission.k8s.io/"
|
||||
// MutationAuditAnnotationPrefix is a prefix for presisting webhook mutation existence in audit annotation.
|
||||
MutationAuditAnnotationPrefix = "mutation.webhook.admission.k8s.io/"
|
||||
)
|
||||
|
||||
var encodingjson = json.CaseSensitiveJsonIterator()
|
||||
|
||||
type mutatingDispatcher struct {
|
||||
cm *webhook.ClientManager
|
||||
cm *webhookutil.ClientManager
|
||||
plugin *Plugin
|
||||
}
|
||||
|
||||
func newMutatingDispatcher(p *Plugin) func(cm *webhook.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhook.ClientManager) generic.Dispatcher {
|
||||
func newMutatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return &mutatingDispatcher{cm, p}
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &mutatingDispatcher{}
|
||||
|
||||
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr *generic.VersionedAttributes, relevantHooks []*v1beta1.Webhook) error {
|
||||
for _, hook := range relevantHooks {
|
||||
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
reinvokeCtx := attr.GetReinvocationContext()
|
||||
var webhookReinvokeCtx *webhookReinvokeContext
|
||||
if v := reinvokeCtx.Value(PluginName); v != nil {
|
||||
webhookReinvokeCtx = v.(*webhookReinvokeContext)
|
||||
} else {
|
||||
webhookReinvokeCtx = &webhookReinvokeContext{}
|
||||
reinvokeCtx.SetValue(PluginName, webhookReinvokeCtx)
|
||||
}
|
||||
|
||||
if reinvokeCtx.IsReinvoke() && webhookReinvokeCtx.IsOutputChangedSinceLastWebhookInvocation(attr.GetObject()) {
|
||||
// If the object has changed, we know the in-tree plugin re-invocations have mutated the object,
|
||||
// and we need to reinvoke all eligible webhooks.
|
||||
webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
}
|
||||
defer func() {
|
||||
webhookReinvokeCtx.SetLastWebhookInvocationOutput(attr.GetObject())
|
||||
}()
|
||||
var versionedAttr *generic.VersionedAttributes
|
||||
for i, hook := range hooks {
|
||||
attrForCheck := attr
|
||||
if versionedAttr != nil {
|
||||
attrForCheck = versionedAttr
|
||||
}
|
||||
invocation, statusErr := a.plugin.ShouldCallHook(hook, attrForCheck, o)
|
||||
if statusErr != nil {
|
||||
return statusErr
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
hook, ok := invocation.Webhook.GetMutatingWebhook()
|
||||
if !ok {
|
||||
return fmt.Errorf("mutating webhook dispatch requires v1beta1.MutatingWebhook, but got %T", hook)
|
||||
}
|
||||
// This means that during reinvocation, a webhook will not be
|
||||
// called for the first time. For example, if the webhook is
|
||||
// skipped in the first round because of mismatching labels,
|
||||
// even if the labels become matching, the webhook does not
|
||||
// get called during reinvocation.
|
||||
if reinvokeCtx.IsReinvoke() && !webhookReinvokeCtx.ShouldReinvokeWebhook(invocation.Webhook.GetUID()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if versionedAttr == nil {
|
||||
// First webhook, create versioned attributes
|
||||
var err error
|
||||
if versionedAttr, err = generic.NewVersionedAttributes(attr, invocation.Kind, o); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
// Subsequent webhook, convert existing versioned attributes to this webhook's version
|
||||
if err := generic.ConvertVersionedAttributes(versionedAttr, invocation.Kind, o); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
err := a.callAttrMutatingHook(ctx, hook, attr)
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr.Attributes, "admit", hook.Name)
|
||||
round := 0
|
||||
if reinvokeCtx.IsReinvoke() {
|
||||
round = 1
|
||||
}
|
||||
changed, err := a.callAttrMutatingHook(ctx, hook, invocation, versionedAttr, o, round, i)
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
rejected := false
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *webhookutil.ErrCallingWebhook:
|
||||
if !ignoreClientCallFailures {
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, 0)
|
||||
}
|
||||
case *webhookutil.ErrWebhookRejection:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code))
|
||||
default:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0)
|
||||
}
|
||||
}
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), rejected, versionedAttr.Attributes, "admit", hook.Name)
|
||||
if changed {
|
||||
// Patch had changed the object. Prepare to reinvoke all previous webhooks that are eligible for re-invocation.
|
||||
webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
}
|
||||
if hook.ReinvocationPolicy != nil && *hook.ReinvocationPolicy == v1beta1.IfNeededReinvocationPolicy {
|
||||
webhookReinvokeCtx.AddReinvocableWebhookToPreviouslyInvoked(invocation.Webhook.GetUID())
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok {
|
||||
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
utilruntime.HandleError(callErr)
|
||||
continue
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// parent context is canceled or timed out, no point in continuing
|
||||
return apierrors.NewTimeoutError("request did not complete within requested timeout", 0)
|
||||
default:
|
||||
// individual webhook timed out, but parent context did not, continue
|
||||
continue
|
||||
}
|
||||
}
|
||||
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
return apierrors.NewInternalError(err)
|
||||
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
|
||||
return rejectionErr.Status
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// convert attr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||
if attr.VersionedObject != nil {
|
||||
return a.plugin.scheme.Convert(attr.VersionedObject, attr.Attributes.GetObject(), nil)
|
||||
// convert versionedAttr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||
if versionedAttr != nil && versionedAttr.VersionedObject != nil && versionedAttr.Dirty {
|
||||
return o.GetObjectConvertor().Convert(versionedAttr.VersionedObject, versionedAttr.Attributes.GetObject(), nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// note that callAttrMutatingHook updates attr
|
||||
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error {
|
||||
if attr.IsDryRun() {
|
||||
|
||||
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.MutatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces, round, idx int) (bool, error) {
|
||||
configurationName := invocation.Webhook.GetConfigurationName()
|
||||
annotator := newWebhookAnnotator(attr, round, idx, h.Name, configurationName)
|
||||
changed := false
|
||||
defer func() { annotator.addMutationAnnotation(changed) }()
|
||||
if attr.Attributes.IsDryRun() {
|
||||
if h.SideEffects == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
}
|
||||
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
|
||||
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
return false, webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Make the webhook request
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
|
||||
if err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
// Make the webhook request
|
||||
client, err := invocation.Webhook.GetRESTClient(a.cm)
|
||||
if err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace := utiltrace.New("Call mutating webhook",
|
||||
utiltrace.Field{"configuration", configurationName},
|
||||
utiltrace.Field{"webhook", h.Name},
|
||||
utiltrace.Field{"resource", attr.GetResource()},
|
||||
utiltrace.Field{"subresource", attr.GetSubresource()},
|
||||
utiltrace.Field{"operation", attr.GetOperation()},
|
||||
utiltrace.Field{"UID", uid})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
// if the webhook has a specific timeout, wrap the context to apply it
|
||||
if h.TimeoutSeconds != nil {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
if response.Response == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
r := client.Post().Context(ctx).Body(request)
|
||||
|
||||
// if the context has a deadline, set it as a parameter to inform the backend
|
||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||
// compute the timeout
|
||||
if timeout := time.Until(deadline); timeout > 0 {
|
||||
// if it's not an even number of seconds, round up to the nearest second
|
||||
if truncated := timeout.Truncate(time.Second); truncated != timeout {
|
||||
timeout = truncated + time.Second
|
||||
}
|
||||
// set the timeout
|
||||
r.Timeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range response.Response.AuditAnnotations {
|
||||
if err := r.Do().Into(response); err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace.Step("Request completed")
|
||||
|
||||
result, err := webhookrequest.VerifyAdmissionResponse(uid, true, response)
|
||||
if err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
|
||||
for k, v := range result.AuditAnnotations {
|
||||
key := h.Name + "/" + k
|
||||
if err := attr.AddAnnotation(key, v); err != nil {
|
||||
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for mutating webhook %s: %v", key, v, h.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !response.Response.Allowed {
|
||||
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
|
||||
if !result.Allowed {
|
||||
return false, &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
||||
|
||||
patchJS := response.Response.Patch
|
||||
if len(patchJS) == 0 {
|
||||
return nil
|
||||
if len(result.Patch) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
patchObj, err := jsonpatch.DecodePatch(patchJS)
|
||||
patchObj, err := jsonpatch.DecodePatch(result.Patch)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if len(patchObj) == 0 {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// if a non-empty patch was provided, and we have no object we can apply it to (e.g. a DELETE admission operation), error
|
||||
if attr.VersionedObject == nil {
|
||||
return apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
|
||||
return false, apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
|
||||
}
|
||||
|
||||
objJS, err := runtime.Encode(a.plugin.jsonSerializer, attr.VersionedObject)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
patchedJS, err := patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
var patchedJS []byte
|
||||
jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), false)
|
||||
switch result.PatchType {
|
||||
// VerifyAdmissionResponse normalizes to v1 patch types, regardless of the AdmissionReview version used
|
||||
case admissionv1.PatchTypeJSONPatch:
|
||||
objJS, err := runtime.Encode(jsonSerializer, attr.VersionedObject)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
patchedJS, err = patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
default:
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("unsupported patch type %q", result.PatchType)}
|
||||
}
|
||||
|
||||
var newVersionedObject runtime.Object
|
||||
@@ -150,17 +311,115 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
|
||||
// They are represented as Unstructured.
|
||||
newVersionedObject = &unstructured.Unstructured{}
|
||||
} else {
|
||||
newVersionedObject, err = a.plugin.scheme.New(attr.GetKind())
|
||||
newVersionedObject, err = o.GetObjectCreater().New(attr.VersionedKind)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: if we have multiple mutating webhooks, we can remember the json
|
||||
// instead of encoding and decoding for each one.
|
||||
if _, _, err := a.plugin.jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
if newVersionedObject, _, err = jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
changed = !apiequality.Semantic.DeepEqual(attr.VersionedObject, newVersionedObject)
|
||||
trace.Step("Patch applied")
|
||||
annotator.addPatchAnnotation(patchObj, result.PatchType)
|
||||
attr.Dirty = true
|
||||
attr.VersionedObject = newVersionedObject
|
||||
a.plugin.scheme.Default(attr.VersionedObject)
|
||||
return nil
|
||||
o.GetObjectDefaulter().Default(attr.VersionedObject)
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
type webhookAnnotator struct {
|
||||
attr *generic.VersionedAttributes
|
||||
patchAnnotationKey string
|
||||
mutationAnnotationKey string
|
||||
webhook string
|
||||
configuration string
|
||||
}
|
||||
|
||||
func newWebhookAnnotator(attr *generic.VersionedAttributes, round, idx int, webhook, configuration string) *webhookAnnotator {
|
||||
return &webhookAnnotator{
|
||||
attr: attr,
|
||||
patchAnnotationKey: fmt.Sprintf("%sround_%d_index_%d", PatchAuditAnnotationPrefix, round, idx),
|
||||
mutationAnnotationKey: fmt.Sprintf("%sround_%d_index_%d", MutationAuditAnnotationPrefix, round, idx),
|
||||
webhook: webhook,
|
||||
configuration: configuration,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhookAnnotator) addMutationAnnotation(mutated bool) {
|
||||
if w.attr == nil || w.attr.Attributes == nil {
|
||||
return
|
||||
}
|
||||
value, err := mutationAnnotationValue(w.configuration, w.webhook, mutated)
|
||||
if err != nil {
|
||||
klog.Warningf("unexpected error composing mutating webhook annotation: %v", err)
|
||||
return
|
||||
}
|
||||
if err := w.attr.Attributes.AddAnnotation(w.mutationAnnotationKey, value); err != nil {
|
||||
klog.Warningf("failed to set mutation annotation for mutating webhook key %s to %s: %v", w.mutationAnnotationKey, value, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhookAnnotator) addPatchAnnotation(patch interface{}, patchType admissionv1.PatchType) {
|
||||
if w.attr == nil || w.attr.Attributes == nil {
|
||||
return
|
||||
}
|
||||
var value string
|
||||
var err error
|
||||
switch patchType {
|
||||
case admissionv1.PatchTypeJSONPatch:
|
||||
value, err = jsonPatchAnnotationValue(w.configuration, w.webhook, patch)
|
||||
if err != nil {
|
||||
klog.Warningf("unexpected error composing mutating webhook JSON patch annotation: %v", err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
klog.Warningf("unsupported patch type for mutating webhook annotation: %v", patchType)
|
||||
return
|
||||
}
|
||||
if err := w.attr.Attributes.AddAnnotationWithLevel(w.patchAnnotationKey, value, auditinternal.LevelRequest); err != nil {
|
||||
// NOTE: we don't log actual patch in kube-apiserver log to avoid potentially
|
||||
// leaking information
|
||||
klog.Warningf("failed to set patch annotation for mutating webhook key %s; confugiration name: %s, webhook name: %s", w.patchAnnotationKey, w.configuration, w.webhook)
|
||||
}
|
||||
}
|
||||
|
||||
// MutationAuditAnnotation logs if a webhook invocation mutated the request object
|
||||
type MutationAuditAnnotation struct {
|
||||
Configuration string `json:"configuration"`
|
||||
Webhook string `json:"webhook"`
|
||||
Mutated bool `json:"mutated"`
|
||||
}
|
||||
|
||||
// PatchAuditAnnotation logs a patch from a mutating webhook
|
||||
type PatchAuditAnnotation struct {
|
||||
Configuration string `json:"configuration"`
|
||||
Webhook string `json:"webhook"`
|
||||
Patch interface{} `json:"patch,omitempty"`
|
||||
PatchType string `json:"patchType,omitempty"`
|
||||
}
|
||||
|
||||
func mutationAnnotationValue(configuration, webhook string, mutated bool) (string, error) {
|
||||
m := MutationAuditAnnotation{
|
||||
Configuration: configuration,
|
||||
Webhook: webhook,
|
||||
Mutated: mutated,
|
||||
}
|
||||
bytes, err := encodingjson.Marshal(m)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func jsonPatchAnnotationValue(configuration, webhook string, patch interface{}) (string, error) {
|
||||
p := PatchAuditAnnotation{
|
||||
Configuration: configuration,
|
||||
Webhook: webhook,
|
||||
Patch: patch,
|
||||
PatchType: string(admissionv1.PatchTypeJSONPatch),
|
||||
}
|
||||
bytes, err := encodingjson.Marshal(p)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
26
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go
generated
vendored
26
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go
generated
vendored
@@ -17,11 +17,9 @@ limitations under the License.
|
||||
package mutating
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
@@ -47,9 +45,6 @@ func Register(plugins *admission.Plugins) {
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*generic.Webhook
|
||||
|
||||
scheme *runtime.Scheme
|
||||
jsonSerializer *json.Serializer
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
@@ -67,30 +62,15 @@ func NewMutatingWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
|
||||
func (a *Plugin) SetScheme(scheme *runtime.Scheme) {
|
||||
a.Webhook.SetScheme(scheme)
|
||||
if scheme != nil {
|
||||
a.scheme = scheme
|
||||
a.jsonSerializer = json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (a *Plugin) ValidateInitialization() error {
|
||||
if err := a.Webhook.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
if a.scheme == nil {
|
||||
return fmt.Errorf("scheme is not properly setup")
|
||||
}
|
||||
if a.jsonSerializer == nil {
|
||||
return fmt.Errorf("jsonSerializer is not properly setup")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Admit(attr admission.Attributes) error {
|
||||
return a.Webhook.Dispatch(attr)
|
||||
func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Webhook.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
68
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go
generated
vendored
Normal file
68
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2019 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 mutating
|
||||
|
||||
import (
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
type webhookReinvokeContext struct {
|
||||
// lastWebhookOutput holds the result of the last webhook admission plugin call
|
||||
lastWebhookOutput runtime.Object
|
||||
// previouslyInvokedReinvocableWebhooks holds the set of webhooks that have been invoked and
|
||||
// should be reinvoked if a later mutation occurs
|
||||
previouslyInvokedReinvocableWebhooks sets.String
|
||||
// reinvokeWebhooks holds the set of webhooks that should be reinvoked
|
||||
reinvokeWebhooks sets.String
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) ShouldReinvokeWebhook(webhook string) bool {
|
||||
return rc.reinvokeWebhooks.Has(webhook)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) IsOutputChangedSinceLastWebhookInvocation(object runtime.Object) bool {
|
||||
return !apiequality.Semantic.DeepEqual(rc.lastWebhookOutput, object)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) SetLastWebhookInvocationOutput(object runtime.Object) {
|
||||
if object == nil {
|
||||
rc.lastWebhookOutput = nil
|
||||
return
|
||||
}
|
||||
rc.lastWebhookOutput = object.DeepCopyObject()
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) AddReinvocableWebhookToPreviouslyInvoked(webhook string) {
|
||||
if rc.previouslyInvokedReinvocableWebhooks == nil {
|
||||
rc.previouslyInvokedReinvocableWebhooks = sets.NewString()
|
||||
}
|
||||
rc.previouslyInvokedReinvocableWebhooks.Insert(webhook)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() {
|
||||
if len(rc.previouslyInvokedReinvocableWebhooks) > 0 {
|
||||
if rc.reinvokeWebhooks == nil {
|
||||
rc.reinvokeWebhooks = sets.NewString()
|
||||
}
|
||||
for s := range rc.previouslyInvokedReinvocableWebhooks {
|
||||
rc.reinvokeWebhooks.Insert(s)
|
||||
}
|
||||
rc.previouslyInvokedReinvocableWebhooks = sets.NewString()
|
||||
}
|
||||
}
|
||||
17
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go
generated
vendored
17
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go
generated
vendored
@@ -19,13 +19,13 @@ package namespace
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
)
|
||||
@@ -86,7 +86,7 @@ func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]stri
|
||||
|
||||
// MatchNamespaceSelector decideds whether the request matches the
|
||||
// namespaceSelctor of the webhook. Only when they match, the webhook is called.
|
||||
func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
func (m *Matcher) MatchNamespaceSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
namespaceName := attr.GetNamespace()
|
||||
if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" {
|
||||
// If the request is about a cluster scoped resource, and it is not a
|
||||
@@ -95,6 +95,14 @@ func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attr
|
||||
// Also update the comment in types.go
|
||||
return true, nil
|
||||
}
|
||||
selector, err := h.GetParsedNamespaceSelector()
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
if selector.Empty() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
namespaceLabels, err := m.GetNamespaceLabels(attr)
|
||||
// this means the namespace is not found, for backwards compatibility,
|
||||
// return a 404
|
||||
@@ -108,10 +116,5 @@ func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attr
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
// TODO: adding an LRU cache to cache the translation
|
||||
selector, err := metav1.LabelSelectorAsSelector(h.NamespaceSelector)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
return selector.Matches(labels.Set(namespaceLabels)), nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
Copyright 2019 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.
|
||||
@@ -14,11 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
// OmitEmpty is an interface for flags to report whether their underlying value
|
||||
// is "empty." If a flag implements OmitEmpty and returns true for a call to Empty(),
|
||||
// it is assumed that flag may be omitted from the command line.
|
||||
type OmitEmpty interface {
|
||||
Empty() bool
|
||||
}
|
||||
// Package object defines the utilities that are used by the webhook plugin to
|
||||
// decide if a webhook should run, as long as either the old object or the new
|
||||
// object has labels matching the webhook config's objectSelector.
|
||||
package object // import "k8s.io/apiserver/pkg/admission/plugin/webhook/object"
|
||||
57
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go
generated
vendored
Normal file
57
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2019 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 object
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// Matcher decides if a request selected by the ObjectSelector.
|
||||
type Matcher struct {
|
||||
}
|
||||
|
||||
func matchObject(obj runtime.Object, selector labels.Selector) bool {
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
klog.V(5).Infof("cannot access metadata of %v: %v", obj, err)
|
||||
return false
|
||||
}
|
||||
return selector.Matches(labels.Set(accessor.GetLabels()))
|
||||
|
||||
}
|
||||
|
||||
// MatchObjectSelector decideds whether the request matches the ObjectSelector
|
||||
// of the webhook. Only when they match, the webhook is called.
|
||||
func (m *Matcher) MatchObjectSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
selector, err := h.GetParsedObjectSelector()
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
if selector.Empty() {
|
||||
return true, nil
|
||||
}
|
||||
return matchObject(attr.GetObject(), selector) || matchObject(attr.GetOldObject(), selector), nil
|
||||
}
|
||||
235
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
235
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
@@ -17,18 +17,145 @@ limitations under the License.
|
||||
package request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
)
|
||||
|
||||
// CreateAdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.AdmissionReview {
|
||||
gvk := attr.GetKind()
|
||||
gvr := attr.GetResource()
|
||||
// AdmissionResponse contains the fields extracted from an AdmissionReview response
|
||||
type AdmissionResponse struct {
|
||||
AuditAnnotations map[string]string
|
||||
Allowed bool
|
||||
Patch []byte
|
||||
PatchType admissionv1.PatchType
|
||||
Result *metav1.Status
|
||||
}
|
||||
|
||||
// VerifyAdmissionResponse checks the validity of the provided admission review object, and returns the
|
||||
// audit annotations, whether the response allowed the request, any provided patch/patchType/status,
|
||||
// or an error if the provided admission review was not valid.
|
||||
func VerifyAdmissionResponse(uid types.UID, mutating bool, review runtime.Object) (*AdmissionResponse, error) {
|
||||
switch r := review.(type) {
|
||||
case *admissionv1.AdmissionReview:
|
||||
if r.Response == nil {
|
||||
return nil, fmt.Errorf("webhook response was absent")
|
||||
}
|
||||
|
||||
// Verify UID matches
|
||||
if r.Response.UID != uid {
|
||||
return nil, fmt.Errorf("expected response.uid=%q, got %q", uid, r.Response.UID)
|
||||
}
|
||||
|
||||
// Verify GVK
|
||||
v1GVK := admissionv1.SchemeGroupVersion.WithKind("AdmissionReview")
|
||||
if r.GroupVersionKind() != v1GVK {
|
||||
return nil, fmt.Errorf("expected webhook response of %v, got %v", v1GVK.String(), r.GroupVersionKind().String())
|
||||
}
|
||||
|
||||
patch := []byte(nil)
|
||||
patchType := admissionv1.PatchType("")
|
||||
|
||||
if mutating {
|
||||
// Ensure a mutating webhook provides both patch and patchType together
|
||||
if len(r.Response.Patch) > 0 && r.Response.PatchType == nil {
|
||||
return nil, fmt.Errorf("webhook returned response.patch but not response.patchType")
|
||||
}
|
||||
if len(r.Response.Patch) == 0 && r.Response.PatchType != nil {
|
||||
return nil, fmt.Errorf("webhook returned response.patchType but not response.patch")
|
||||
}
|
||||
patch = r.Response.Patch
|
||||
if r.Response.PatchType != nil {
|
||||
patchType = *r.Response.PatchType
|
||||
if len(patchType) == 0 {
|
||||
return nil, fmt.Errorf("webhook returned invalid response.patchType of %q", patchType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure a validating webhook doesn't return patch or patchType
|
||||
if len(r.Response.Patch) > 0 {
|
||||
return nil, fmt.Errorf("validating webhook may not return response.patch")
|
||||
}
|
||||
if r.Response.PatchType != nil {
|
||||
return nil, fmt.Errorf("validating webhook may not return response.patchType")
|
||||
}
|
||||
}
|
||||
|
||||
return &AdmissionResponse{
|
||||
AuditAnnotations: r.Response.AuditAnnotations,
|
||||
Allowed: r.Response.Allowed,
|
||||
Patch: patch,
|
||||
PatchType: patchType,
|
||||
Result: r.Response.Result,
|
||||
}, nil
|
||||
|
||||
case *admissionv1beta1.AdmissionReview:
|
||||
if r.Response == nil {
|
||||
return nil, fmt.Errorf("webhook response was absent")
|
||||
}
|
||||
|
||||
// Response GVK and response.uid were not verified in v1beta1 handling, allow any
|
||||
|
||||
patch := []byte(nil)
|
||||
patchType := admissionv1.PatchType("")
|
||||
if mutating {
|
||||
patch = r.Response.Patch
|
||||
if len(r.Response.Patch) > 0 {
|
||||
// patch type was not verified in v1beta1 admissionreview handling. pin to only supported version if a patch is provided.
|
||||
patchType = admissionv1.PatchTypeJSONPatch
|
||||
}
|
||||
}
|
||||
|
||||
return &AdmissionResponse{
|
||||
AuditAnnotations: r.Response.AuditAnnotations,
|
||||
Allowed: r.Response.Allowed,
|
||||
Patch: patch,
|
||||
PatchType: patchType,
|
||||
Result: r.Response.Result,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected response type %T", review)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAdmissionObjects returns the unique request uid, the AdmissionReview object to send the webhook and to decode the response into,
|
||||
// or an error if the webhook does not support receiving any of the admission review versions we know to send
|
||||
func CreateAdmissionObjects(versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) (uid types.UID, request, response runtime.Object, err error) {
|
||||
for _, version := range invocation.Webhook.GetAdmissionReviewVersions() {
|
||||
switch version {
|
||||
case admissionv1.SchemeGroupVersion.Version:
|
||||
uid := types.UID(uuid.NewUUID())
|
||||
request := CreateV1AdmissionReview(uid, versionedAttributes, invocation)
|
||||
response := &admissionv1.AdmissionReview{}
|
||||
return uid, request, response, nil
|
||||
|
||||
case admissionv1beta1.SchemeGroupVersion.Version:
|
||||
uid := types.UID(uuid.NewUUID())
|
||||
request := CreateV1beta1AdmissionReview(uid, versionedAttributes, invocation)
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
return uid, request, response, nil
|
||||
|
||||
}
|
||||
}
|
||||
return "", nil, nil, fmt.Errorf("webhook does not accept known AdmissionReview versions (v1, v1beta1)")
|
||||
}
|
||||
|
||||
// CreateV1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateV1AdmissionReview(uid types.UID, versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1.AdmissionReview {
|
||||
attr := versionedAttributes.Attributes
|
||||
gvk := invocation.Kind
|
||||
gvr := invocation.Resource
|
||||
subresource := invocation.Subresource
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
@@ -43,9 +170,9 @@ func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.A
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
|
||||
return admissionv1beta1.AdmissionReview{
|
||||
Request: &admissionv1beta1.AdmissionRequest{
|
||||
UID: uuid.NewUUID(),
|
||||
return &admissionv1.AdmissionReview{
|
||||
Request: &admissionv1.AdmissionRequest{
|
||||
UID: uid,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
@@ -56,18 +183,98 @@ func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.A
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: attr.GetSubresource(),
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1beta1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
Object: runtime.RawExtension{
|
||||
Object: attr.VersionedObject,
|
||||
Object: versionedAttributes.VersionedObject,
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Object: attr.VersionedOldObject,
|
||||
Object: versionedAttributes.VersionedOldObject,
|
||||
},
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateV1beta1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateV1beta1AdmissionReview(uid types.UID, versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1beta1.AdmissionReview {
|
||||
attr := versionedAttributes.Attributes
|
||||
gvk := invocation.Kind
|
||||
gvr := invocation.Resource
|
||||
subresource := invocation.Subresource
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
dryRun := attr.IsDryRun()
|
||||
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
|
||||
return &admissionv1beta1.AdmissionReview{
|
||||
Request: &admissionv1beta1.AdmissionRequest{
|
||||
UID: uid,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1beta1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
Object: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedObject,
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedOldObject,
|
||||
},
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
24
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules.go
generated
vendored
24
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules.go
generated
vendored
@@ -20,6 +20,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
@@ -31,7 +33,8 @@ type Matcher struct {
|
||||
|
||||
// Matches returns if the Attr matches the Rule.
|
||||
func (r *Matcher) Matches() bool {
|
||||
return r.operation() &&
|
||||
return r.scope() &&
|
||||
r.operation() &&
|
||||
r.group() &&
|
||||
r.version() &&
|
||||
r.resource()
|
||||
@@ -50,6 +53,25 @@ func exactOrWildcard(items []string, requested string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var namespaceResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}
|
||||
|
||||
func (r *Matcher) scope() bool {
|
||||
if r.Rule.Scope == nil || *r.Rule.Scope == v1beta1.AllScopes {
|
||||
return true
|
||||
}
|
||||
// attr.GetNamespace() is set to the name of the namespace for requests of the namespace object itself.
|
||||
switch *r.Rule.Scope {
|
||||
case v1beta1.NamespacedScope:
|
||||
// first make sure that we are not requesting a namespace object (namespace objects are cluster-scoped)
|
||||
return r.Attr.GetResource() != namespaceResource && r.Attr.GetNamespace() != metav1.NamespaceNone
|
||||
case v1beta1.ClusterScope:
|
||||
// also return true if the request is for a namespace object (namespace objects are cluster-scoped)
|
||||
return r.Attr.GetResource() == namespaceResource || r.Attr.GetNamespace() == metav1.NamespaceNone
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Matcher) group() bool {
|
||||
return exactOrWildcard(r.Rule.APIGroups, r.Attr.GetResource().Group)
|
||||
}
|
||||
|
||||
42
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go
generated
vendored
42
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go
generated
vendored
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
)
|
||||
|
||||
// HookClientConfigForWebhook construct a webhook.ClientConfig using a v1beta1.Webhook API object.
|
||||
// webhook.ClientConfig is used to create a HookClient and the purpose of the config struct is to
|
||||
// share that with other packages that need to create a HookClient.
|
||||
func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig {
|
||||
ret := webhook.ClientConfig{Name: w.Name, CABundle: w.ClientConfig.CABundle}
|
||||
if w.ClientConfig.URL != nil {
|
||||
ret.URL = *w.ClientConfig.URL
|
||||
}
|
||||
if w.ClientConfig.Service != nil {
|
||||
ret.Service = &webhook.ClientConfigService{
|
||||
Name: w.ClientConfig.Service.Name,
|
||||
Namespace: w.ClientConfig.Service.Namespace,
|
||||
}
|
||||
if w.ClientConfig.Service.Path != nil {
|
||||
ret.Service.Path = *w.ClientConfig.Service.Path
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
164
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
164
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
@@ -22,47 +22,108 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/klog"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
type validatingDispatcher struct {
|
||||
cm *webhook.ClientManager
|
||||
cm *webhookutil.ClientManager
|
||||
plugin *Plugin
|
||||
}
|
||||
|
||||
func newValidatingDispatcher(cm *webhook.ClientManager) generic.Dispatcher {
|
||||
return &validatingDispatcher{cm}
|
||||
func newValidatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return &validatingDispatcher{cm, p}
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &validatingDispatcher{}
|
||||
|
||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.VersionedAttributes, relevantHooks []*v1beta1.Webhook) error {
|
||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
var relevantHooks []*generic.WebhookInvocation
|
||||
// Construct all the versions we need to call our webhooks
|
||||
versionedAttrs := map[schema.GroupVersionKind]*generic.VersionedAttributes{}
|
||||
for _, hook := range hooks {
|
||||
invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
relevantHooks = append(relevantHooks, invocation)
|
||||
// If we already have this version, continue
|
||||
if _, ok := versionedAttrs[invocation.Kind]; ok {
|
||||
continue
|
||||
}
|
||||
versionedAttr, err := generic.NewVersionedAttributes(attr, invocation.Kind, o)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttrs[invocation.Kind] = versionedAttr
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
// no matching hooks
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the request has already timed out before spawning remote calls
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// parent context is canceled or timed out, no point in continuing
|
||||
return apierrors.NewTimeoutError("request did not complete within requested timeout", 0)
|
||||
default:
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
errCh := make(chan error, len(relevantHooks))
|
||||
wg.Add(len(relevantHooks))
|
||||
for i := range relevantHooks {
|
||||
go func(hook *v1beta1.Webhook) {
|
||||
go func(invocation *generic.WebhookInvocation) {
|
||||
defer wg.Done()
|
||||
|
||||
hook, ok := invocation.Webhook.GetValidatingWebhook()
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("validating webhook dispatch requires v1beta1.ValidatingWebhook, but got %T", hook))
|
||||
return
|
||||
}
|
||||
versionedAttr := versionedAttrs[invocation.Kind]
|
||||
t := time.Now()
|
||||
err := d.callHook(ctx, hook, attr)
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr.Attributes, "validating", hook.Name)
|
||||
err := d.callHook(ctx, hook, invocation, versionedAttr)
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
rejected := false
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *webhookutil.ErrCallingWebhook:
|
||||
if !ignoreClientCallFailures {
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, 0)
|
||||
}
|
||||
case *webhookutil.ErrWebhookRejection:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code))
|
||||
default:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0)
|
||||
}
|
||||
}
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), rejected, versionedAttr.Attributes, "validating", hook.Name)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok {
|
||||
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
utilruntime.HandleError(callErr)
|
||||
@@ -74,6 +135,9 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.Versi
|
||||
return
|
||||
}
|
||||
|
||||
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
|
||||
err = rejectionErr.Status
|
||||
}
|
||||
klog.Warningf("rejected by webhook %q: %#v", hook.Name, err)
|
||||
errCh <- err
|
||||
}(relevantHooks[i])
|
||||
@@ -97,38 +161,74 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.Versi
|
||||
return errs[0]
|
||||
}
|
||||
|
||||
func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error {
|
||||
if attr.IsDryRun() {
|
||||
func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.ValidatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error {
|
||||
if attr.Attributes.IsDryRun() {
|
||||
if h.SideEffects == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
}
|
||||
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
|
||||
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Make the webhook request
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
|
||||
if err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
// Make the webhook request
|
||||
client, err := invocation.Webhook.GetRESTClient(d.cm)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace := utiltrace.New("Call validating webhook",
|
||||
utiltrace.Field{"configuration", invocation.Webhook.GetConfigurationName()},
|
||||
utiltrace.Field{"webhook", h.Name},
|
||||
utiltrace.Field{"resource", attr.GetResource()},
|
||||
utiltrace.Field{"subresource", attr.GetSubresource()},
|
||||
utiltrace.Field{"operation", attr.GetOperation()},
|
||||
utiltrace.Field{"UID", uid})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
// if the webhook has a specific timeout, wrap the context to apply it
|
||||
if h.TimeoutSeconds != nil {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
if response.Response == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
r := client.Post().Context(ctx).Body(request)
|
||||
|
||||
// if the context has a deadline, set it as a parameter to inform the backend
|
||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||
// compute the timeout
|
||||
if timeout := time.Until(deadline); timeout > 0 {
|
||||
// if it's not an even number of seconds, round up to the nearest second
|
||||
if truncated := timeout.Truncate(time.Second); truncated != timeout {
|
||||
timeout = truncated + time.Second
|
||||
}
|
||||
// set the timeout
|
||||
r.Timeout(timeout)
|
||||
}
|
||||
}
|
||||
for k, v := range response.Response.AuditAnnotations {
|
||||
|
||||
if err := r.Do().Into(response); err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace.Step("Request completed")
|
||||
|
||||
result, err := webhookrequest.VerifyAdmissionResponse(uid, false, response)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
|
||||
for k, v := range result.AuditAnnotations {
|
||||
key := h.Name + "/" + k
|
||||
if err := attr.AddAnnotation(key, v); err != nil {
|
||||
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, v, h.Name, err)
|
||||
}
|
||||
}
|
||||
if response.Response.Allowed {
|
||||
if result.Allowed {
|
||||
return nil
|
||||
}
|
||||
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
|
||||
return &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
||||
|
||||
11
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
11
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package validating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
@@ -51,14 +52,16 @@ var _ admission.ValidationInterface = &Plugin{}
|
||||
// NewValidatingAdmissionWebhook returns a generic admission webhook plugin.
|
||||
func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
|
||||
webhook, err := generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher)
|
||||
p := &Plugin{}
|
||||
var err error
|
||||
p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher(p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Plugin{webhook}, nil
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Validate(attr admission.Attributes) error {
|
||||
return a.Webhook.Dispatch(attr)
|
||||
func (a *Plugin) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Webhook.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/admission/plugins.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/admission/plugins.go
generated
vendored
@@ -160,7 +160,7 @@ func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigPro
|
||||
if len(validationPlugins) != 0 {
|
||||
klog.Infof("Loaded %d validating admission controller(s) successfully in the following order: %s.", len(validationPlugins), strings.Join(validationPlugins, ","))
|
||||
}
|
||||
return chainAdmissionHandler(handlers), nil
|
||||
return newReinvocationHandler(chainAdmissionHandler(handlers)), nil
|
||||
}
|
||||
|
||||
// InitPlugin creates an instance of the named interface.
|
||||
|
||||
64
vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
generated
vendored
Normal file
64
vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2019 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 admission
|
||||
|
||||
import "context"
|
||||
|
||||
// newReinvocationHandler creates a handler that wraps the provided admission chain and reinvokes it
|
||||
// if needed according to re-invocation policy of the webhooks.
|
||||
func newReinvocationHandler(admissionChain Interface) Interface {
|
||||
return &reinvoker{admissionChain}
|
||||
}
|
||||
|
||||
type reinvoker struct {
|
||||
admissionChain Interface
|
||||
}
|
||||
|
||||
// Admit performs an admission control check using the wrapped admission chain, reinvoking the
|
||||
// admission chain if needed according to the reinvocation policy. Plugins are expected to check
|
||||
// the admission attributes' reinvocation context against their reinvocation policy to decide if
|
||||
// they should re-run, and to update the reinvocation context if they perform any mutations.
|
||||
func (r *reinvoker) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if mutator, ok := r.admissionChain.(MutationInterface); ok {
|
||||
err := mutator.Admit(ctx, a, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := a.GetReinvocationContext()
|
||||
if s.ShouldReinvoke() {
|
||||
s.SetIsReinvoke()
|
||||
// Calling admit a second time will reinvoke all in-tree plugins
|
||||
// as well as any webhook plugins that need to be reinvoked based on the
|
||||
// reinvocation policy.
|
||||
return mutator.Admit(ctx, a, o)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs an admission control check using the wrapped admission chain, and returns immediately on first error.
|
||||
func (r *reinvoker) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if validator, ok := r.admissionChain.(ValidationInterface); ok {
|
||||
return validator.Validate(ctx, a, o)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles will return true if any of the admission chain handlers handle the given operation.
|
||||
func (r *reinvoker) Handles(operation Operation) bool {
|
||||
return r.admissionChain.Handles(operation)
|
||||
}
|
||||
47
vendor/k8s.io/apiserver/pkg/admission/util.go
generated
vendored
Normal file
47
vendor/k8s.io/apiserver/pkg/admission/util.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2019 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 admission
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
type RuntimeObjectInterfaces struct {
|
||||
runtime.ObjectCreater
|
||||
runtime.ObjectTyper
|
||||
runtime.ObjectDefaulter
|
||||
runtime.ObjectConvertor
|
||||
runtime.EquivalentResourceMapper
|
||||
}
|
||||
|
||||
func NewObjectInterfacesFromScheme(scheme *runtime.Scheme) ObjectInterfaces {
|
||||
return &RuntimeObjectInterfaces{scheme, scheme, scheme, scheme, runtime.NewEquivalentResourceRegistry()}
|
||||
}
|
||||
|
||||
func (r *RuntimeObjectInterfaces) GetObjectCreater() runtime.ObjectCreater {
|
||||
return r.ObjectCreater
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetObjectTyper() runtime.ObjectTyper {
|
||||
return r.ObjectTyper
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetObjectDefaulter() runtime.ObjectDefaulter {
|
||||
return r.ObjectDefaulter
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetObjectConvertor() runtime.ObjectConvertor {
|
||||
return r.ObjectConvertor
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetEquivalentResourceMapper() runtime.EquivalentResourceMapper {
|
||||
return r.EquivalentResourceMapper
|
||||
}
|
||||
1
vendor/k8s.io/apiserver/pkg/apis/apiserver/register.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/apis/apiserver/register.go
generated
vendored
@@ -45,6 +45,7 @@ var (
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&AdmissionConfiguration{},
|
||||
&EgressSelectorConfiguration{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
49
vendor/k8s.io/apiserver/pkg/apis/apiserver/types.go
generated
vendored
49
vendor/k8s.io/apiserver/pkg/apis/apiserver/types.go
generated
vendored
@@ -48,3 +48,52 @@ type AdmissionPluginConfiguration struct {
|
||||
// +optional
|
||||
Configuration *runtime.Unknown
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// EgressSelectorConfiguration provides versioned configuration for egress selector clients.
|
||||
type EgressSelectorConfiguration struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
// EgressSelections contains a list of egress selection client configurations
|
||||
EgressSelections []EgressSelection
|
||||
}
|
||||
|
||||
// EgressSelection provides the configuration for a single egress selection client.
|
||||
type EgressSelection struct {
|
||||
// Name is the name of the egress selection.
|
||||
// Currently supported values are "Master", "Etcd" and "Cluster"
|
||||
Name string
|
||||
|
||||
// Connection is the exact information used to configure the egress selection
|
||||
Connection Connection
|
||||
}
|
||||
|
||||
// Connection provides the configuration for a single egress selection client.
|
||||
type Connection struct {
|
||||
// Type is the type of connection used to connect from client to konnectivity server.
|
||||
// Currently supported values are "http-connect" and "direct".
|
||||
Type string
|
||||
|
||||
// httpConnect is the config needed to use http-connect to the konnectivity server.
|
||||
// +optional
|
||||
HTTPConnect *HTTPConnectConfig
|
||||
}
|
||||
|
||||
type HTTPConnectConfig struct {
|
||||
// URL is the location of the konnectivity server to connect to.
|
||||
// As an example it might be "https://127.0.0.1:8131"
|
||||
URL string
|
||||
|
||||
// CABundle is the file location of the CA to be used to determine trust with the konnectivity server.
|
||||
// +optional
|
||||
CABundle string
|
||||
|
||||
// ClientKey is the file location of the client key to be used in mtls handshakes with the konnectivity server.
|
||||
// +optional
|
||||
ClientKey string
|
||||
|
||||
// ClientCert is the file location of the client certificate to be used in mtls handshakes with the konnectivity server.
|
||||
// +optional
|
||||
ClientCert string
|
||||
}
|
||||
|
||||
1
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/register.go
generated
vendored
@@ -46,6 +46,7 @@ func init() {
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&AdmissionConfiguration{},
|
||||
&EgressSelectorConfiguration{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
||||
60
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go
generated
vendored
60
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go
generated
vendored
@@ -48,3 +48,63 @@ type AdmissionPluginConfiguration struct {
|
||||
// +optional
|
||||
Configuration *runtime.Unknown `json:"configuration"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// EgressSelectorConfiguration provides versioned configuration for egress selector clients.
|
||||
type EgressSelectorConfiguration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// connectionServices contains a list of egress selection client configurations
|
||||
EgressSelections []EgressSelection `json:"egressSelections"`
|
||||
}
|
||||
|
||||
// EgressSelection provides the configuration for a single egress selection client.
|
||||
type EgressSelection struct {
|
||||
// name is the name of the egress selection.
|
||||
// Currently supported values are "Master", "Etcd" and "Cluster"
|
||||
Name string `json:"name"`
|
||||
|
||||
// connection is the exact information used to configure the egress selection
|
||||
Connection Connection `json:"connection"`
|
||||
}
|
||||
|
||||
// Connection provides the configuration for a single egress selection client.
|
||||
type Connection struct {
|
||||
// type is the type of connection used to connect from client to network/konnectivity server.
|
||||
// Currently supported values are "http-connect" and "direct".
|
||||
Type string `json:"type"`
|
||||
|
||||
// httpConnect is the config needed to use http-connect to the konnectivity server.
|
||||
// Absence when the type is "http-connect" will cause an error
|
||||
// Presence when the type is "direct" will also cause an error
|
||||
// +optional
|
||||
HTTPConnect *HTTPConnectConfig `json:"httpConnect,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPConnectConfig struct {
|
||||
// url is the location of the proxy server to connect to.
|
||||
// As an example it might be "https://127.0.0.1:8131"
|
||||
URL string `json:"url"`
|
||||
|
||||
// caBundle is the file location of the CA to be used to determine trust with the konnectivity server.
|
||||
// Must be absent/empty http-connect using the plain http
|
||||
// Must be configured for http-connect using the https protocol
|
||||
// Misconfiguration will cause an error
|
||||
// +optional
|
||||
CABundle string `json:"caBundle,omitempty"`
|
||||
|
||||
// clientKey is the file location of the client key to be used in mtls handshakes with the konnectivity server.
|
||||
// Must be absent/empty http-connect using the plain http
|
||||
// Must be configured for http-connect using the https protocol
|
||||
// Misconfiguration will cause an error
|
||||
// +optional
|
||||
ClientKey string `json:"clientKey,omitempty"`
|
||||
|
||||
// clientCert is the file location of the client certificate to be used in mtls handshakes with the konnectivity server.
|
||||
// Must be absent/empty http-connect using the plain http
|
||||
// Must be configured for http-connect using the https protocol
|
||||
// Misconfiguration will cause an error
|
||||
// +optional
|
||||
ClientCert string `json:"clientCert,omitempty"`
|
||||
}
|
||||
|
||||
134
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go
generated
vendored
134
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.conversion.go
generated
vendored
@@ -55,6 +55,46 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Connection)(nil), (*apiserver.Connection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_Connection_To_apiserver_Connection(a.(*Connection), b.(*apiserver.Connection), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*apiserver.Connection)(nil), (*Connection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_apiserver_Connection_To_v1alpha1_Connection(a.(*apiserver.Connection), b.(*Connection), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*EgressSelection)(nil), (*apiserver.EgressSelection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(a.(*EgressSelection), b.(*apiserver.EgressSelection), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*apiserver.EgressSelection)(nil), (*EgressSelection)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(a.(*apiserver.EgressSelection), b.(*EgressSelection), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*EgressSelectorConfiguration)(nil), (*apiserver.EgressSelectorConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(a.(*EgressSelectorConfiguration), b.(*apiserver.EgressSelectorConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*apiserver.EgressSelectorConfiguration)(nil), (*EgressSelectorConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(a.(*apiserver.EgressSelectorConfiguration), b.(*EgressSelectorConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*HTTPConnectConfig)(nil), (*apiserver.HTTPConnectConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(a.(*HTTPConnectConfig), b.(*apiserver.HTTPConnectConfig), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*apiserver.HTTPConnectConfig)(nil), (*HTTPConnectConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(a.(*apiserver.HTTPConnectConfig), b.(*HTTPConnectConfig), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -101,3 +141,97 @@ func autoConvert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPlu
|
||||
func Convert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPluginConfiguration(in *apiserver.AdmissionPluginConfiguration, out *AdmissionPluginConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_apiserver_AdmissionPluginConfiguration_To_v1alpha1_AdmissionPluginConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_Connection_To_apiserver_Connection(in *Connection, out *apiserver.Connection, s conversion.Scope) error {
|
||||
out.Type = in.Type
|
||||
out.HTTPConnect = (*apiserver.HTTPConnectConfig)(unsafe.Pointer(in.HTTPConnect))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_Connection_To_apiserver_Connection is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_Connection_To_apiserver_Connection(in *Connection, out *apiserver.Connection, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_Connection_To_apiserver_Connection(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_apiserver_Connection_To_v1alpha1_Connection(in *apiserver.Connection, out *Connection, s conversion.Scope) error {
|
||||
out.Type = in.Type
|
||||
out.HTTPConnect = (*HTTPConnectConfig)(unsafe.Pointer(in.HTTPConnect))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_apiserver_Connection_To_v1alpha1_Connection is an autogenerated conversion function.
|
||||
func Convert_apiserver_Connection_To_v1alpha1_Connection(in *apiserver.Connection, out *Connection, s conversion.Scope) error {
|
||||
return autoConvert_apiserver_Connection_To_v1alpha1_Connection(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(in *EgressSelection, out *apiserver.EgressSelection, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
if err := Convert_v1alpha1_Connection_To_apiserver_Connection(&in.Connection, &out.Connection, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_EgressSelection_To_apiserver_EgressSelection is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(in *EgressSelection, out *apiserver.EgressSelection, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_EgressSelection_To_apiserver_EgressSelection(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(in *apiserver.EgressSelection, out *EgressSelection, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
if err := Convert_apiserver_Connection_To_v1alpha1_Connection(&in.Connection, &out.Connection, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_apiserver_EgressSelection_To_v1alpha1_EgressSelection is an autogenerated conversion function.
|
||||
func Convert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(in *apiserver.EgressSelection, out *EgressSelection, s conversion.Scope) error {
|
||||
return autoConvert_apiserver_EgressSelection_To_v1alpha1_EgressSelection(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(in *EgressSelectorConfiguration, out *apiserver.EgressSelectorConfiguration, s conversion.Scope) error {
|
||||
out.EgressSelections = *(*[]apiserver.EgressSelection)(unsafe.Pointer(&in.EgressSelections))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(in *EgressSelectorConfiguration, out *apiserver.EgressSelectorConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_EgressSelectorConfiguration_To_apiserver_EgressSelectorConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in *apiserver.EgressSelectorConfiguration, out *EgressSelectorConfiguration, s conversion.Scope) error {
|
||||
out.EgressSelections = *(*[]EgressSelection)(unsafe.Pointer(&in.EgressSelections))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration is an autogenerated conversion function.
|
||||
func Convert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in *apiserver.EgressSelectorConfiguration, out *EgressSelectorConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_apiserver_EgressSelectorConfiguration_To_v1alpha1_EgressSelectorConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(in *HTTPConnectConfig, out *apiserver.HTTPConnectConfig, s conversion.Scope) error {
|
||||
out.URL = in.URL
|
||||
out.CABundle = in.CABundle
|
||||
out.ClientKey = in.ClientKey
|
||||
out.ClientCert = in.ClientCert
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(in *HTTPConnectConfig, out *apiserver.HTTPConnectConfig, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_HTTPConnectConfig_To_apiserver_HTTPConnectConfig(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(in *apiserver.HTTPConnectConfig, out *HTTPConnectConfig, s conversion.Scope) error {
|
||||
out.URL = in.URL
|
||||
out.CABundle = in.CABundle
|
||||
out.ClientKey = in.ClientKey
|
||||
out.ClientCert = in.ClientCert
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig is an autogenerated conversion function.
|
||||
func Convert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(in *apiserver.HTTPConnectConfig, out *HTTPConnectConfig, s conversion.Scope) error {
|
||||
return autoConvert_apiserver_HTTPConnectConfig_To_v1alpha1_HTTPConnectConfig(in, out, s)
|
||||
}
|
||||
|
||||
86
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go
generated
vendored
86
vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/zz_generated.deepcopy.go
generated
vendored
@@ -76,3 +76,89 @@ func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Connection) DeepCopyInto(out *Connection) {
|
||||
*out = *in
|
||||
if in.HTTPConnect != nil {
|
||||
in, out := &in.HTTPConnect, &out.HTTPConnect
|
||||
*out = new(HTTPConnectConfig)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
|
||||
func (in *Connection) DeepCopy() *Connection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Connection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EgressSelection) DeepCopyInto(out *EgressSelection) {
|
||||
*out = *in
|
||||
in.Connection.DeepCopyInto(&out.Connection)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelection.
|
||||
func (in *EgressSelection) DeepCopy() *EgressSelection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EgressSelection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EgressSelectorConfiguration) DeepCopyInto(out *EgressSelectorConfiguration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.EgressSelections != nil {
|
||||
in, out := &in.EgressSelections, &out.EgressSelections
|
||||
*out = make([]EgressSelection, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelectorConfiguration.
|
||||
func (in *EgressSelectorConfiguration) DeepCopy() *EgressSelectorConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EgressSelectorConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HTTPConnectConfig) DeepCopyInto(out *HTTPConnectConfig) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPConnectConfig.
|
||||
func (in *HTTPConnectConfig) DeepCopy() *HTTPConnectConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HTTPConnectConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
86
vendor/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go
generated
vendored
86
vendor/k8s.io/apiserver/pkg/apis/apiserver/zz_generated.deepcopy.go
generated
vendored
@@ -76,3 +76,89 @@ func (in *AdmissionPluginConfiguration) DeepCopy() *AdmissionPluginConfiguration
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Connection) DeepCopyInto(out *Connection) {
|
||||
*out = *in
|
||||
if in.HTTPConnect != nil {
|
||||
in, out := &in.HTTPConnect, &out.HTTPConnect
|
||||
*out = new(HTTPConnectConfig)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
|
||||
func (in *Connection) DeepCopy() *Connection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Connection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EgressSelection) DeepCopyInto(out *EgressSelection) {
|
||||
*out = *in
|
||||
in.Connection.DeepCopyInto(&out.Connection)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelection.
|
||||
func (in *EgressSelection) DeepCopy() *EgressSelection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EgressSelection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EgressSelectorConfiguration) DeepCopyInto(out *EgressSelectorConfiguration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.EgressSelections != nil {
|
||||
in, out := &in.EgressSelections, &out.EgressSelections
|
||||
*out = make([]EgressSelection, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelectorConfiguration.
|
||||
func (in *EgressSelectorConfiguration) DeepCopy() *EgressSelectorConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EgressSelectorConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HTTPConnectConfig) DeepCopyInto(out *HTTPConnectConfig) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPConnectConfig.
|
||||
func (in *HTTPConnectConfig) DeepCopy() *HTTPConnectConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HTTPConnectConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/apis/audit/OWNERS
generated
vendored
2
vendor/k8s.io/apiserver/pkg/apis/audit/OWNERS
generated
vendored
@@ -1,3 +1,5 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
# approval on api packages bubbles to api-approvers
|
||||
reviewers:
|
||||
- sig-auth-audit-approvers
|
||||
|
||||
1
vendor/k8s.io/apiserver/pkg/apis/audit/v1/doc.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/apis/audit/v1/doc.go
generated
vendored
@@ -15,6 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:protobuf-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/audit
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
1547
vendor/k8s.io/apiserver/pkg/apis/audit/v1/generated.pb.go
generated
vendored
1547
vendor/k8s.io/apiserver/pkg/apis/audit/v1/generated.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
4
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.deepcopy.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/apis/audit/v1/zz_generated.deepcopy.go
generated
vendored
@@ -95,7 +95,7 @@ func (in *Event) DeepCopyObject() runtime.Object {
|
||||
func (in *EventList) DeepCopyInto(out *EventList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Event, len(*in))
|
||||
@@ -208,7 +208,7 @@ func (in *Policy) DeepCopyObject() runtime.Object {
|
||||
func (in *PolicyList) DeepCopyInto(out *PolicyList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Policy, len(*in))
|
||||
|
||||
1
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/doc.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/doc.go
generated
vendored
@@ -15,6 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:protobuf-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/audit
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
1589
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go
generated
vendored
1589
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
4
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.deepcopy.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.deepcopy.go
generated
vendored
@@ -97,7 +97,7 @@ func (in *Event) DeepCopyObject() runtime.Object {
|
||||
func (in *EventList) DeepCopyInto(out *EventList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Event, len(*in))
|
||||
@@ -210,7 +210,7 @@ func (in *Policy) DeepCopyObject() runtime.Object {
|
||||
func (in *PolicyList) DeepCopyInto(out *PolicyList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Policy, len(*in))
|
||||
|
||||
1
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go
generated
vendored
@@ -15,6 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:protobuf-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/audit
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
1605
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go
generated
vendored
1605
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
4
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go
generated
vendored
@@ -97,7 +97,7 @@ func (in *Event) DeepCopyObject() runtime.Object {
|
||||
func (in *EventList) DeepCopyInto(out *EventList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Event, len(*in))
|
||||
@@ -210,7 +210,7 @@ func (in *Policy) DeepCopyObject() runtime.Object {
|
||||
func (in *PolicyList) DeepCopyInto(out *PolicyList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Policy, len(*in))
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/apis/audit/validation/validation.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/apis/audit/validation/validation.go
generated
vendored
@@ -100,7 +100,7 @@ func validateResources(groupResources []audit.GroupResources, fldPath *field.Pat
|
||||
// The empty string represents the core API group.
|
||||
if len(groupResource.Group) != 0 {
|
||||
// Group names must be lower case and be valid DNS subdomains.
|
||||
// reference: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md
|
||||
// reference: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md
|
||||
// an error is returned for group name like rbac.authorization.k8s.io/v1beta1
|
||||
// rbac.authorization.k8s.io is the valid one
|
||||
if msgs := validation.NameIsDNSSubdomain(groupResource.Group, false); len(msgs) != 0 {
|
||||
|
||||
4
vendor/k8s.io/apiserver/pkg/apis/audit/zz_generated.deepcopy.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/apis/audit/zz_generated.deepcopy.go
generated
vendored
@@ -94,7 +94,7 @@ func (in *Event) DeepCopyObject() runtime.Object {
|
||||
func (in *EventList) DeepCopyInto(out *EventList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Event, len(*in))
|
||||
@@ -227,7 +227,7 @@ func (in *Policy) DeepCopyObject() runtime.Object {
|
||||
func (in *PolicyList) DeepCopyInto(out *PolicyList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Policy, len(*in))
|
||||
|
||||
40
vendor/k8s.io/apiserver/pkg/apis/config/types.go
generated
vendored
40
vendor/k8s.io/apiserver/pkg/apis/config/types.go
generated
vendored
@@ -20,43 +20,6 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// LeaderElectionConfiguration defines the configuration of leader election
|
||||
// clients for components that can run with leader election enabled.
|
||||
type LeaderElectionConfiguration struct {
|
||||
// leaderElect enables a leader election client to gain leadership
|
||||
// before executing the main loop. Enable this when running replicated
|
||||
// components for high availability.
|
||||
LeaderElect bool
|
||||
// leaseDuration is the duration that non-leader candidates will wait
|
||||
// after observing a leadership renewal until attempting to acquire
|
||||
// leadership of a led but unrenewed leader slot. This is effectively the
|
||||
// maximum duration that a leader can be stopped before it is replaced
|
||||
// by another candidate. This is only applicable if leader election is
|
||||
// enabled.
|
||||
LeaseDuration metav1.Duration
|
||||
// renewDeadline is the interval between attempts by the acting master to
|
||||
// renew a leadership slot before it stops leading. This must be less
|
||||
// than or equal to the lease duration. This is only applicable if leader
|
||||
// election is enabled.
|
||||
RenewDeadline metav1.Duration
|
||||
// retryPeriod is the duration the clients should wait between attempting
|
||||
// acquisition and renewal of a leadership. This is only applicable if
|
||||
// leader election is enabled.
|
||||
RetryPeriod metav1.Duration
|
||||
// resourceLock indicates the resource object type that will be used to lock
|
||||
// during leader election cycles.
|
||||
ResourceLock string
|
||||
}
|
||||
|
||||
// DebuggingConfiguration holds configuration for Debugging related features.
|
||||
type DebuggingConfiguration struct {
|
||||
// enableProfiling enables profiling via web interface host:port/debug/pprof/
|
||||
EnableProfiling bool
|
||||
// enableContentionProfiling enables lock contention profiling, if
|
||||
// enableProfiling is true.
|
||||
EnableContentionProfiling bool
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// EncryptionConfiguration stores the complete configuration for encryption providers.
|
||||
@@ -123,4 +86,7 @@ type KMSConfiguration struct {
|
||||
CacheSize int32
|
||||
// endpoint is the gRPC server listening address, for example "unix:///var/run/kms-provider.sock".
|
||||
Endpoint string
|
||||
// Timeout for gRPC calls to kms-plugin (ex. 5s). The default is 3 seconds.
|
||||
// +optional
|
||||
Timeout *metav1.Duration
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
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.
|
||||
@@ -14,4 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package etcd // import "k8s.io/apiserver/pkg/storage/etcd"
|
||||
// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/config
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=apiserver.config.k8s.io
|
||||
|
||||
// Package v1 is the v1 version of the API.
|
||||
package v1
|
||||
52
vendor/k8s.io/apiserver/pkg/apis/config/v1/register.go
generated
vendored
Normal file
52
vendor/k8s.io/apiserver/pkg/apis/config/v1/register.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package.
|
||||
const GroupName = "apiserver.config.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||
|
||||
var (
|
||||
// SchemeBuilder points to a list of functions added to Scheme.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
// AddToScheme adds this group to a scheme.
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addKnownTypes)
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&EncryptionConfiguration{},
|
||||
)
|
||||
// also register into the v1 group as EncryptionConfig (due to a docs bug)
|
||||
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "EncryptionConfig"}, &EncryptionConfiguration{})
|
||||
return nil
|
||||
}
|
||||
92
vendor/k8s.io/apiserver/pkg/apis/config/v1/types.go
generated
vendored
Normal file
92
vendor/k8s.io/apiserver/pkg/apis/config/v1/types.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// EncryptionConfiguration stores the complete configuration for encryption providers.
|
||||
type EncryptionConfiguration struct {
|
||||
metav1.TypeMeta
|
||||
// resources is a list containing resources, and their corresponding encryption providers.
|
||||
Resources []ResourceConfiguration `json:"resources"`
|
||||
}
|
||||
|
||||
// ResourceConfiguration stores per resource configuration.
|
||||
type ResourceConfiguration struct {
|
||||
// resources is a list of kubernetes resources which have to be encrypted.
|
||||
Resources []string `json:"resources"`
|
||||
// providers is a list of transformers to be used for reading and writing the resources to disk.
|
||||
// eg: aesgcm, aescbc, secretbox, identity.
|
||||
Providers []ProviderConfiguration `json:"providers"`
|
||||
}
|
||||
|
||||
// ProviderConfiguration stores the provided configuration for an encryption provider.
|
||||
type ProviderConfiguration struct {
|
||||
// aesgcm is the configuration for the AES-GCM transformer.
|
||||
AESGCM *AESConfiguration `json:"aesgcm,omitempty"`
|
||||
// aescbc is the configuration for the AES-CBC transformer.
|
||||
AESCBC *AESConfiguration `json:"aescbc,omitempty"`
|
||||
// secretbox is the configuration for the Secretbox based transformer.
|
||||
Secretbox *SecretboxConfiguration `json:"secretbox,omitempty"`
|
||||
// identity is the (empty) configuration for the identity transformer.
|
||||
Identity *IdentityConfiguration `json:"identity,omitempty"`
|
||||
// kms contains the name, cache size and path to configuration file for a KMS based envelope transformer.
|
||||
KMS *KMSConfiguration `json:"kms,omitempty"`
|
||||
}
|
||||
|
||||
// AESConfiguration contains the API configuration for an AES transformer.
|
||||
type AESConfiguration struct {
|
||||
// keys is a list of keys to be used for creating the AES transformer.
|
||||
// Each key has to be 32 bytes long for AES-CBC and 16, 24 or 32 bytes for AES-GCM.
|
||||
Keys []Key `json:"keys"`
|
||||
}
|
||||
|
||||
// SecretboxConfiguration contains the API configuration for an Secretbox transformer.
|
||||
type SecretboxConfiguration struct {
|
||||
// keys is a list of keys to be used for creating the Secretbox transformer.
|
||||
// Each key has to be 32 bytes long.
|
||||
Keys []Key `json:"keys"`
|
||||
}
|
||||
|
||||
// Key contains name and secret of the provided key for a transformer.
|
||||
type Key struct {
|
||||
// name is the name of the key to be used while storing data to disk.
|
||||
Name string `json:"name"`
|
||||
// secret is the actual key, encoded in base64.
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
// IdentityConfiguration is an empty struct to allow identity transformer in provider configuration.
|
||||
type IdentityConfiguration struct{}
|
||||
|
||||
// KMSConfiguration contains the name, cache size and path to configuration file for a KMS based envelope transformer.
|
||||
type KMSConfiguration struct {
|
||||
// name is the name of the KMS plugin to be used.
|
||||
Name string `json:"name"`
|
||||
// cacheSize is the maximum number of secrets which are cached in memory. The default value is 1000.
|
||||
// +optional
|
||||
CacheSize int32 `json:"cachesize,omitempty"`
|
||||
// endpoint is the gRPC server listening address, for example "unix:///var/run/kms-provider.sock".
|
||||
Endpoint string `json:"endpoint"`
|
||||
// Timeout for gRPC calls to kms-plugin (ex. 5s). The default is 3 seconds.
|
||||
// +optional
|
||||
Timeout *metav1.Duration `json:"timeout,omitempty"`
|
||||
}
|
||||
296
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.conversion.go
generated
vendored
Normal file
296
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.conversion.go
generated
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
config "k8s.io/apiserver/pkg/apis/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*AESConfiguration)(nil), (*config.AESConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_AESConfiguration_To_config_AESConfiguration(a.(*AESConfiguration), b.(*config.AESConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.AESConfiguration)(nil), (*AESConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_AESConfiguration_To_v1_AESConfiguration(a.(*config.AESConfiguration), b.(*AESConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*EncryptionConfiguration)(nil), (*config.EncryptionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(a.(*EncryptionConfiguration), b.(*config.EncryptionConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.EncryptionConfiguration)(nil), (*EncryptionConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(a.(*config.EncryptionConfiguration), b.(*EncryptionConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*IdentityConfiguration)(nil), (*config.IdentityConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_IdentityConfiguration_To_config_IdentityConfiguration(a.(*IdentityConfiguration), b.(*config.IdentityConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.IdentityConfiguration)(nil), (*IdentityConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_IdentityConfiguration_To_v1_IdentityConfiguration(a.(*config.IdentityConfiguration), b.(*IdentityConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*KMSConfiguration)(nil), (*config.KMSConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_KMSConfiguration_To_config_KMSConfiguration(a.(*KMSConfiguration), b.(*config.KMSConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.KMSConfiguration)(nil), (*KMSConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_KMSConfiguration_To_v1_KMSConfiguration(a.(*config.KMSConfiguration), b.(*KMSConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Key)(nil), (*config.Key)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_Key_To_config_Key(a.(*Key), b.(*config.Key), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.Key)(nil), (*Key)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_Key_To_v1_Key(a.(*config.Key), b.(*Key), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ProviderConfiguration)(nil), (*config.ProviderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ProviderConfiguration_To_config_ProviderConfiguration(a.(*ProviderConfiguration), b.(*config.ProviderConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.ProviderConfiguration)(nil), (*ProviderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_ProviderConfiguration_To_v1_ProviderConfiguration(a.(*config.ProviderConfiguration), b.(*ProviderConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ResourceConfiguration)(nil), (*config.ResourceConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ResourceConfiguration_To_config_ResourceConfiguration(a.(*ResourceConfiguration), b.(*config.ResourceConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.ResourceConfiguration)(nil), (*ResourceConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_ResourceConfiguration_To_v1_ResourceConfiguration(a.(*config.ResourceConfiguration), b.(*ResourceConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*SecretboxConfiguration)(nil), (*config.SecretboxConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(a.(*SecretboxConfiguration), b.(*config.SecretboxConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.SecretboxConfiguration)(nil), (*SecretboxConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(a.(*config.SecretboxConfiguration), b.(*SecretboxConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1_AESConfiguration_To_config_AESConfiguration(in *AESConfiguration, out *config.AESConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]config.Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_AESConfiguration_To_config_AESConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_AESConfiguration_To_config_AESConfiguration(in *AESConfiguration, out *config.AESConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_AESConfiguration_To_config_AESConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_AESConfiguration_To_v1_AESConfiguration(in *config.AESConfiguration, out *AESConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_AESConfiguration_To_v1_AESConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_AESConfiguration_To_v1_AESConfiguration(in *config.AESConfiguration, out *AESConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_AESConfiguration_To_v1_AESConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(in *EncryptionConfiguration, out *config.EncryptionConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]config.ResourceConfiguration)(unsafe.Pointer(&in.Resources))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(in *EncryptionConfiguration, out *config.EncryptionConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_EncryptionConfiguration_To_config_EncryptionConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(in *config.EncryptionConfiguration, out *EncryptionConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]ResourceConfiguration)(unsafe.Pointer(&in.Resources))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(in *config.EncryptionConfiguration, out *EncryptionConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_EncryptionConfiguration_To_v1_EncryptionConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_IdentityConfiguration_To_config_IdentityConfiguration(in *IdentityConfiguration, out *config.IdentityConfiguration, s conversion.Scope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_IdentityConfiguration_To_config_IdentityConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_IdentityConfiguration_To_config_IdentityConfiguration(in *IdentityConfiguration, out *config.IdentityConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_IdentityConfiguration_To_config_IdentityConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_IdentityConfiguration_To_v1_IdentityConfiguration(in *config.IdentityConfiguration, out *IdentityConfiguration, s conversion.Scope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_IdentityConfiguration_To_v1_IdentityConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_IdentityConfiguration_To_v1_IdentityConfiguration(in *config.IdentityConfiguration, out *IdentityConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_IdentityConfiguration_To_v1_IdentityConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_KMSConfiguration_To_config_KMSConfiguration(in *KMSConfiguration, out *config.KMSConfiguration, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.CacheSize = in.CacheSize
|
||||
out.Endpoint = in.Endpoint
|
||||
out.Timeout = (*metav1.Duration)(unsafe.Pointer(in.Timeout))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_KMSConfiguration_To_config_KMSConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_KMSConfiguration_To_config_KMSConfiguration(in *KMSConfiguration, out *config.KMSConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_KMSConfiguration_To_config_KMSConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_KMSConfiguration_To_v1_KMSConfiguration(in *config.KMSConfiguration, out *KMSConfiguration, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.CacheSize = in.CacheSize
|
||||
out.Endpoint = in.Endpoint
|
||||
out.Timeout = (*metav1.Duration)(unsafe.Pointer(in.Timeout))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_KMSConfiguration_To_v1_KMSConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_KMSConfiguration_To_v1_KMSConfiguration(in *config.KMSConfiguration, out *KMSConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_KMSConfiguration_To_v1_KMSConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_Key_To_config_Key(in *Key, out *config.Key, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Secret = in.Secret
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_Key_To_config_Key is an autogenerated conversion function.
|
||||
func Convert_v1_Key_To_config_Key(in *Key, out *config.Key, s conversion.Scope) error {
|
||||
return autoConvert_v1_Key_To_config_Key(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_Key_To_v1_Key(in *config.Key, out *Key, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Secret = in.Secret
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_Key_To_v1_Key is an autogenerated conversion function.
|
||||
func Convert_config_Key_To_v1_Key(in *config.Key, out *Key, s conversion.Scope) error {
|
||||
return autoConvert_config_Key_To_v1_Key(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ProviderConfiguration_To_config_ProviderConfiguration(in *ProviderConfiguration, out *config.ProviderConfiguration, s conversion.Scope) error {
|
||||
out.AESGCM = (*config.AESConfiguration)(unsafe.Pointer(in.AESGCM))
|
||||
out.AESCBC = (*config.AESConfiguration)(unsafe.Pointer(in.AESCBC))
|
||||
out.Secretbox = (*config.SecretboxConfiguration)(unsafe.Pointer(in.Secretbox))
|
||||
out.Identity = (*config.IdentityConfiguration)(unsafe.Pointer(in.Identity))
|
||||
out.KMS = (*config.KMSConfiguration)(unsafe.Pointer(in.KMS))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ProviderConfiguration_To_config_ProviderConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_ProviderConfiguration_To_config_ProviderConfiguration(in *ProviderConfiguration, out *config.ProviderConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_ProviderConfiguration_To_config_ProviderConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_ProviderConfiguration_To_v1_ProviderConfiguration(in *config.ProviderConfiguration, out *ProviderConfiguration, s conversion.Scope) error {
|
||||
out.AESGCM = (*AESConfiguration)(unsafe.Pointer(in.AESGCM))
|
||||
out.AESCBC = (*AESConfiguration)(unsafe.Pointer(in.AESCBC))
|
||||
out.Secretbox = (*SecretboxConfiguration)(unsafe.Pointer(in.Secretbox))
|
||||
out.Identity = (*IdentityConfiguration)(unsafe.Pointer(in.Identity))
|
||||
out.KMS = (*KMSConfiguration)(unsafe.Pointer(in.KMS))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_ProviderConfiguration_To_v1_ProviderConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_ProviderConfiguration_To_v1_ProviderConfiguration(in *config.ProviderConfiguration, out *ProviderConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_ProviderConfiguration_To_v1_ProviderConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ResourceConfiguration_To_config_ResourceConfiguration(in *ResourceConfiguration, out *config.ResourceConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources))
|
||||
out.Providers = *(*[]config.ProviderConfiguration)(unsafe.Pointer(&in.Providers))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ResourceConfiguration_To_config_ResourceConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_ResourceConfiguration_To_config_ResourceConfiguration(in *ResourceConfiguration, out *config.ResourceConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_ResourceConfiguration_To_config_ResourceConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_ResourceConfiguration_To_v1_ResourceConfiguration(in *config.ResourceConfiguration, out *ResourceConfiguration, s conversion.Scope) error {
|
||||
out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources))
|
||||
out.Providers = *(*[]ProviderConfiguration)(unsafe.Pointer(&in.Providers))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_ResourceConfiguration_To_v1_ResourceConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_ResourceConfiguration_To_v1_ResourceConfiguration(in *config.ResourceConfiguration, out *ResourceConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_ResourceConfiguration_To_v1_ResourceConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(in *SecretboxConfiguration, out *config.SecretboxConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]config.Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(in *SecretboxConfiguration, out *config.SecretboxConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1_SecretboxConfiguration_To_config_SecretboxConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(in *config.SecretboxConfiguration, out *SecretboxConfiguration, s conversion.Scope) error {
|
||||
out.Keys = *(*[]Key)(unsafe.Pointer(&in.Keys))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(in *config.SecretboxConfiguration, out *SecretboxConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_SecretboxConfiguration_To_v1_SecretboxConfiguration(in, out, s)
|
||||
}
|
||||
222
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
222
vendor/k8s.io/apiserver/pkg/apis/config/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AESConfiguration) DeepCopyInto(out *AESConfiguration) {
|
||||
*out = *in
|
||||
if in.Keys != nil {
|
||||
in, out := &in.Keys, &out.Keys
|
||||
*out = make([]Key, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AESConfiguration.
|
||||
func (in *AESConfiguration) DeepCopy() *AESConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AESConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EncryptionConfiguration) DeepCopyInto(out *EncryptionConfiguration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = make([]ResourceConfiguration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EncryptionConfiguration.
|
||||
func (in *EncryptionConfiguration) DeepCopy() *EncryptionConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EncryptionConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *EncryptionConfiguration) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IdentityConfiguration) DeepCopyInto(out *IdentityConfiguration) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityConfiguration.
|
||||
func (in *IdentityConfiguration) DeepCopy() *IdentityConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IdentityConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KMSConfiguration) DeepCopyInto(out *KMSConfiguration) {
|
||||
*out = *in
|
||||
if in.Timeout != nil {
|
||||
in, out := &in.Timeout, &out.Timeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KMSConfiguration.
|
||||
func (in *KMSConfiguration) DeepCopy() *KMSConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KMSConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Key) DeepCopyInto(out *Key) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Key.
|
||||
func (in *Key) DeepCopy() *Key {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Key)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProviderConfiguration) DeepCopyInto(out *ProviderConfiguration) {
|
||||
*out = *in
|
||||
if in.AESGCM != nil {
|
||||
in, out := &in.AESGCM, &out.AESGCM
|
||||
*out = new(AESConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AESCBC != nil {
|
||||
in, out := &in.AESCBC, &out.AESCBC
|
||||
*out = new(AESConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Secretbox != nil {
|
||||
in, out := &in.Secretbox, &out.Secretbox
|
||||
*out = new(SecretboxConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Identity != nil {
|
||||
in, out := &in.Identity, &out.Identity
|
||||
*out = new(IdentityConfiguration)
|
||||
**out = **in
|
||||
}
|
||||
if in.KMS != nil {
|
||||
in, out := &in.KMS, &out.KMS
|
||||
*out = new(KMSConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfiguration.
|
||||
func (in *ProviderConfiguration) DeepCopy() *ProviderConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProviderConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceConfiguration) DeepCopyInto(out *ResourceConfiguration) {
|
||||
*out = *in
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Providers != nil {
|
||||
in, out := &in.Providers, &out.Providers
|
||||
*out = make([]ProviderConfiguration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceConfiguration.
|
||||
func (in *ResourceConfiguration) DeepCopy() *ResourceConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResourceConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretboxConfiguration) DeepCopyInto(out *SecretboxConfiguration) {
|
||||
*out = *in
|
||||
if in.Keys != nil {
|
||||
in, out := &in.Keys, &out.Keys
|
||||
*out = make([]Key, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretboxConfiguration.
|
||||
func (in *SecretboxConfiguration) DeepCopy() *SecretboxConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecretboxConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
Copyright 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.
|
||||
@@ -14,28 +16,17 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
goflag "flag"
|
||||
"github.com/spf13/pflag"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// NoOp implements goflag.Value and plfag.Value,
|
||||
// but has a noop Set implementation
|
||||
type NoOp struct{}
|
||||
|
||||
var _ goflag.Value = NoOp{}
|
||||
var _ pflag.Value = NoOp{}
|
||||
|
||||
func (NoOp) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (NoOp) Set(val string) error {
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NoOp) Type() string {
|
||||
return "NoOp"
|
||||
}
|
||||
43
vendor/k8s.io/apiserver/pkg/apis/config/zz_generated.deepcopy.go
generated
vendored
43
vendor/k8s.io/apiserver/pkg/apis/config/zz_generated.deepcopy.go
generated
vendored
@@ -21,6 +21,7 @@ limitations under the License.
|
||||
package config
|
||||
|
||||
import (
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -45,22 +46,6 @@ func (in *AESConfiguration) DeepCopy() *AESConfiguration {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DebuggingConfiguration) DeepCopyInto(out *DebuggingConfiguration) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DebuggingConfiguration.
|
||||
func (in *DebuggingConfiguration) DeepCopy() *DebuggingConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DebuggingConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EncryptionConfiguration) DeepCopyInto(out *EncryptionConfiguration) {
|
||||
*out = *in
|
||||
@@ -112,6 +97,11 @@ func (in *IdentityConfiguration) DeepCopy() *IdentityConfiguration {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KMSConfiguration) DeepCopyInto(out *KMSConfiguration) {
|
||||
*out = *in
|
||||
if in.Timeout != nil {
|
||||
in, out := &in.Timeout, &out.Timeout
|
||||
*out = new(v1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -141,25 +131,6 @@ func (in *Key) DeepCopy() *Key {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LeaderElectionConfiguration) DeepCopyInto(out *LeaderElectionConfiguration) {
|
||||
*out = *in
|
||||
out.LeaseDuration = in.LeaseDuration
|
||||
out.RenewDeadline = in.RenewDeadline
|
||||
out.RetryPeriod = in.RetryPeriod
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfiguration.
|
||||
func (in *LeaderElectionConfiguration) DeepCopy() *LeaderElectionConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LeaderElectionConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProviderConfiguration) DeepCopyInto(out *ProviderConfiguration) {
|
||||
*out = *in
|
||||
@@ -186,7 +157,7 @@ func (in *ProviderConfiguration) DeepCopyInto(out *ProviderConfiguration) {
|
||||
if in.KMS != nil {
|
||||
in, out := &in.KMS, &out.KMS
|
||||
*out = new(KMSConfiguration)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/audit/OWNERS
generated
vendored
2
vendor/k8s.io/apiserver/pkg/audit/OWNERS
generated
vendored
@@ -1,3 +1,5 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- sig-auth-audit-approvers
|
||||
reviewers:
|
||||
|
||||
51
vendor/k8s.io/apiserver/pkg/audit/metrics.go
generated
vendored
51
vendor/k8s.io/apiserver/pkg/audit/metrics.go
generated
vendored
@@ -19,8 +19,9 @@ package audit
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
@@ -28,46 +29,58 @@ const (
|
||||
subsystem = "apiserver_audit"
|
||||
)
|
||||
|
||||
/*
|
||||
* By default, all the following metrics are defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
* the metric stability policy.
|
||||
*/
|
||||
var (
|
||||
eventCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Subsystem: subsystem,
|
||||
Name: "event_total",
|
||||
Help: "Counter of audit events generated and sent to the audit backend.",
|
||||
eventCounter = metrics.NewCounter(
|
||||
&metrics.CounterOpts{
|
||||
Subsystem: subsystem,
|
||||
Name: "event_total",
|
||||
Help: "Counter of audit events generated and sent to the audit backend.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
})
|
||||
errorCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
errorCounter = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Subsystem: subsystem,
|
||||
Name: "error_total",
|
||||
Help: "Counter of audit events that failed to be audited properly. " +
|
||||
"Plugin identifies the plugin affected by the error.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"plugin"},
|
||||
)
|
||||
levelCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Subsystem: subsystem,
|
||||
Name: "level_total",
|
||||
Help: "Counter of policy levels for audit events (1 per request).",
|
||||
levelCounter = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Subsystem: subsystem,
|
||||
Name: "level_total",
|
||||
Help: "Counter of policy levels for audit events (1 per request).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"level"},
|
||||
)
|
||||
|
||||
ApiserverAuditDroppedCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
ApiserverAuditDroppedCounter = metrics.NewCounter(
|
||||
&metrics.CounterOpts{
|
||||
Subsystem: subsystem,
|
||||
Name: "requests_rejected_total",
|
||||
Help: "Counter of apiserver requests rejected due to an error " +
|
||||
"in audit logging backend.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(eventCounter)
|
||||
prometheus.MustRegister(errorCounter)
|
||||
prometheus.MustRegister(levelCounter)
|
||||
prometheus.MustRegister(ApiserverAuditDroppedCounter)
|
||||
legacyregistry.MustRegister(eventCounter)
|
||||
legacyregistry.MustRegister(errorCounter)
|
||||
legacyregistry.MustRegister(levelCounter)
|
||||
legacyregistry.MustRegister(ApiserverAuditDroppedCounter)
|
||||
}
|
||||
|
||||
// ObserveEvent updates the relevant prometheus metrics for the generated audit event.
|
||||
|
||||
49
vendor/k8s.io/apiserver/pkg/audit/request.go
generated
vendored
49
vendor/k8s.io/apiserver/pkg/audit/request.go
generated
vendored
@@ -20,13 +20,13 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
"k8s.io/klog"
|
||||
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -117,8 +117,9 @@ func LogRequestObject(ae *auditinternal.Event, obj runtime.Object, gvr schema.Gr
|
||||
if ae.ObjectRef == nil {
|
||||
ae.ObjectRef = &auditinternal.ObjectReference{}
|
||||
}
|
||||
if acc, ok := obj.(metav1.ObjectMetaAccessor); ok {
|
||||
meta := acc.GetObjectMeta()
|
||||
|
||||
// meta.Accessor is more general than ObjectMetaAccessor, but if it fails, we can just skip setting these bits
|
||||
if meta, err := meta.Accessor(obj); err == nil {
|
||||
if len(ae.ObjectRef.Namespace) == 0 {
|
||||
ae.ObjectRef.Namespace = meta.GetNamespace()
|
||||
}
|
||||
@@ -196,22 +197,22 @@ func LogResponseObject(ae *auditinternal.Event, obj runtime.Object, gv schema.Gr
|
||||
}
|
||||
|
||||
func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime.NegotiatedSerializer) (*runtime.Unknown, error) {
|
||||
supported := serializer.SupportedMediaTypes()
|
||||
for i := range supported {
|
||||
if supported[i].MediaType == "application/json" {
|
||||
enc := serializer.EncoderForVersion(supported[i].Serializer, gv)
|
||||
var buf bytes.Buffer
|
||||
if err := enc.Encode(obj, &buf); err != nil {
|
||||
return nil, fmt.Errorf("encoding failed: %v", err)
|
||||
}
|
||||
|
||||
return &runtime.Unknown{
|
||||
Raw: buf.Bytes(),
|
||||
ContentType: runtime.ContentTypeJSON,
|
||||
}, nil
|
||||
}
|
||||
const mediaType = runtime.ContentTypeJSON
|
||||
info, ok := runtime.SerializerInfoForMediaType(serializer.SupportedMediaTypes(), mediaType)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to locate encoder -- %q is not a supported media type", mediaType)
|
||||
}
|
||||
return nil, fmt.Errorf("no json encoder found")
|
||||
|
||||
enc := serializer.EncoderForVersion(info.Serializer, gv)
|
||||
var buf bytes.Buffer
|
||||
if err := enc.Encode(obj, &buf); err != nil {
|
||||
return nil, fmt.Errorf("encoding failed: %v", err)
|
||||
}
|
||||
|
||||
return &runtime.Unknown{
|
||||
Raw: buf.Bytes(),
|
||||
ContentType: runtime.ContentTypeJSON,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LogAnnotation fills in the Annotations according to the key value pair.
|
||||
@@ -229,16 +230,6 @@ func LogAnnotation(ae *auditinternal.Event, key, value string) {
|
||||
ae.Annotations[key] = value
|
||||
}
|
||||
|
||||
// LogAnnotations fills in the Annotations according to the annotations map.
|
||||
func LogAnnotations(ae *auditinternal.Event, annotations map[string]string) {
|
||||
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
||||
return
|
||||
}
|
||||
for key, value := range annotations {
|
||||
LogAnnotation(ae, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// truncate User-Agent if too long, otherwise return it directly.
|
||||
func maybeTruncateUserAgent(req *http.Request) string {
|
||||
ua := req.UserAgent()
|
||||
|
||||
3
vendor/k8s.io/apiserver/pkg/audit/scheme.go
generated
vendored
3
vendor/k8s.io/apiserver/pkg/audit/scheme.go
generated
vendored
@@ -23,6 +23,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/apis/audit/v1"
|
||||
"k8s.io/apiserver/pkg/apis/audit/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/apis/audit/v1beta1"
|
||||
@@ -36,4 +37,6 @@ func init() {
|
||||
utilruntime.Must(v1.AddToScheme(Scheme))
|
||||
utilruntime.Must(v1alpha1.AddToScheme(Scheme))
|
||||
utilruntime.Must(v1beta1.AddToScheme(Scheme))
|
||||
utilruntime.Must(auditinternal.AddToScheme(Scheme))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion, v1alpha1.SchemeGroupVersion))
|
||||
}
|
||||
|
||||
6
vendor/k8s.io/apiserver/pkg/audit/util/conversion.go
generated
vendored
6
vendor/k8s.io/apiserver/pkg/audit/util/conversion.go
generated
vendored
@@ -35,6 +35,12 @@ func HookClientConfigForSink(a *v1alpha1.AuditSink) webhook.ClientConfig {
|
||||
Name: c.Service.Name,
|
||||
Namespace: c.Service.Namespace,
|
||||
}
|
||||
if c.Service.Port != nil {
|
||||
ret.Service.Port = *c.Service.Port
|
||||
} else {
|
||||
ret.Service.Port = 443
|
||||
}
|
||||
|
||||
if c.Service.Path != nil {
|
||||
ret.Service.Path = *c.Service.Path
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/authentication/request/x509/OWNERS
generated
vendored
2
vendor/k8s.io/apiserver/pkg/authentication/request/x509/OWNERS
generated
vendored
@@ -1,3 +1,5 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- sig-auth-certificates-approvers
|
||||
reviewers:
|
||||
|
||||
22
vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go
generated
vendored
22
vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go
generated
vendored
@@ -23,22 +23,33 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
var clientCertificateExpirationHistogram = prometheus.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
/*
|
||||
* By default, the following metric is defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
* the metric stability policy.
|
||||
*/
|
||||
var clientCertificateExpirationHistogram = metrics.NewHistogram(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: "apiserver",
|
||||
Subsystem: "client",
|
||||
Name: "certificate_expiration_seconds",
|
||||
Help: "Distribution of the remaining lifetime on the certificate used to authenticate a request.",
|
||||
Buckets: []float64{
|
||||
0,
|
||||
(30 * time.Minute).Seconds(),
|
||||
(1 * time.Hour).Seconds(),
|
||||
(2 * time.Hour).Seconds(),
|
||||
(6 * time.Hour).Seconds(),
|
||||
(12 * time.Hour).Seconds(),
|
||||
(24 * time.Hour).Seconds(),
|
||||
@@ -50,11 +61,12 @@ var clientCertificateExpirationHistogram = prometheus.NewHistogram(
|
||||
(6 * 30 * 24 * time.Hour).Seconds(),
|
||||
(12 * 30 * 24 * time.Hour).Seconds(),
|
||||
},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(clientCertificateExpirationHistogram)
|
||||
legacyregistry.MustRegister(clientCertificateExpirationHistogram)
|
||||
}
|
||||
|
||||
// UserConversion defines an interface for extracting user info from a client certificate chain
|
||||
|
||||
21
vendor/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go
generated
vendored
21
vendor/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go
generated
vendored
@@ -36,6 +36,27 @@ func MakeUsername(namespace, name string) string {
|
||||
return ServiceAccountUsernamePrefix + namespace + ServiceAccountUsernameSeparator + name
|
||||
}
|
||||
|
||||
// MatchesUsername checks whether the provided username matches the namespace and name without
|
||||
// allocating. Use this when checking a service account namespace and name against a known string.
|
||||
func MatchesUsername(namespace, name string, username string) bool {
|
||||
if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) {
|
||||
return false
|
||||
}
|
||||
username = username[len(ServiceAccountUsernamePrefix):]
|
||||
|
||||
if !strings.HasPrefix(username, namespace) {
|
||||
return false
|
||||
}
|
||||
username = username[len(namespace):]
|
||||
|
||||
if !strings.HasPrefix(username, ServiceAccountUsernameSeparator) {
|
||||
return false
|
||||
}
|
||||
username = username[len(ServiceAccountUsernameSeparator):]
|
||||
|
||||
return username == name
|
||||
}
|
||||
|
||||
var invalidUsernameErr = fmt.Errorf("Username must be in the form %s", MakeUsername("namespace", "name"))
|
||||
|
||||
// SplitUsername returns the namespace and ServiceAccount name embedded in the given username,
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/authentication/user/doc.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/authentication/user/doc.go
generated
vendored
@@ -16,4 +16,4 @@ limitations under the License.
|
||||
|
||||
// Package user contains utilities for dealing with simple user exchange in the auth
|
||||
// packages. The user.Info interface defines an interface for exchanging that info.
|
||||
package user
|
||||
package user // import "k8s.io/apiserver/pkg/authentication/user"
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go
generated
vendored
@@ -56,7 +56,7 @@ type Attributes interface {
|
||||
GetAPIVersion() string
|
||||
|
||||
// IsResourceRequest returns true for requests to API resources, like /api/v1/nodes,
|
||||
// and false for non-resource endpoints like /api, /healthz, and /swaggerapi
|
||||
// and false for non-resource endpoints like /api, /healthz
|
||||
IsResourceRequest() bool
|
||||
|
||||
// GetPath returns the path of the request
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/OWNERS
generated
vendored
2
vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/OWNERS
generated
vendored
@@ -1,3 +1,5 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- deads2k
|
||||
- dims
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/authorization/path/doc.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/authorization/path/doc.go
generated
vendored
@@ -15,4 +15,4 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// Package path contains an authorizer that allows certain paths and path prefixes.
|
||||
package path
|
||||
package path // import "k8s.io/apiserver/pkg/authorization/path"
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/group.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/group.go
generated
vendored
@@ -69,5 +69,5 @@ func (s *APIGroupHandler) handle(req *restful.Request, resp *restful.Response) {
|
||||
}
|
||||
|
||||
func (s *APIGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK, &s.group)
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK, &s.group)
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
generated
vendored
@@ -72,5 +72,5 @@ func (s *legacyRootAPIHandler) handle(req *restful.Request, resp *restful.Respon
|
||||
Versions: []string{"v1"},
|
||||
}
|
||||
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, apiVersions)
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, apiVersions)
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/root.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/root.go
generated
vendored
@@ -111,7 +111,7 @@ func (s *rootAPIsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request)
|
||||
groups[i].ServerAddressByClientCIDRs = serverCIDR
|
||||
}
|
||||
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups})
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups})
|
||||
}
|
||||
|
||||
func (s *rootAPIsHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
40
vendor/k8s.io/apiserver/pkg/endpoints/discovery/storageversionhash.go
generated
vendored
Normal file
40
vendor/k8s.io/apiserver/pkg/endpoints/discovery/storageversionhash.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2019 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 discovery
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// StorageVersionHash calculates the storage version hash for a
|
||||
// <group/version/kind> tuple.
|
||||
// WARNING: this function is subject to change. Clients shouldn't depend on
|
||||
// this function.
|
||||
func StorageVersionHash(group, version, kind string) string {
|
||||
gvk := group + "/" + version + "/" + kind
|
||||
if gvk == "" {
|
||||
return ""
|
||||
}
|
||||
bytes := sha256.Sum256([]byte(gvk))
|
||||
// Assuming there are N kinds in the cluster, and the hash is X-byte long,
|
||||
// the chance of colliding hash P(N,X) approximates to 1-e^(-(N^2)/2^(8X+1)).
|
||||
// P(10,000, 8) ~= 2.7*10^(-12), which is low enough.
|
||||
// See https://en.wikipedia.org/wiki/Birthday_problem#Approximations.
|
||||
return base64.StdEncoding.EncodeToString(
|
||||
bytes[:8])
|
||||
}
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/version.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/version.go
generated
vendored
@@ -78,6 +78,6 @@ func (s *APIVersionHandler) handle(req *restful.Request, resp *restful.Response)
|
||||
}
|
||||
|
||||
func (s *APIVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK,
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK,
|
||||
&metav1.APIResourceList{GroupVersion: s.groupVersion.String(), APIResources: s.apiResourceLister.ListAPIResources()})
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
generated
vendored
@@ -1,3 +1,5 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- deads2k
|
||||
- sttts
|
||||
|
||||
45
vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
generated
vendored
45
vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
generated
vendored
@@ -21,29 +21,53 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"k8s.io/klog"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
/*
|
||||
* By default, all the following metrics are defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
* the metric stability policy.
|
||||
*/
|
||||
const (
|
||||
successLabel = "success"
|
||||
failureLabel = "failure"
|
||||
errorLabel = "error"
|
||||
)
|
||||
|
||||
var (
|
||||
authenticatedUserCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "authenticated_user_requests",
|
||||
Help: "Counter of authenticated requests broken out by username.",
|
||||
authenticatedUserCounter = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Name: "authenticated_user_requests",
|
||||
Help: "Counter of authenticated requests broken out by username.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"username"},
|
||||
)
|
||||
|
||||
authenticatedAttemptsCounter = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Name: "authentication_attempts",
|
||||
Help: "Counter of authenticated attempts.",
|
||||
},
|
||||
[]string{"result"},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(authenticatedUserCounter)
|
||||
legacyregistry.MustRegister(authenticatedUserCounter)
|
||||
legacyregistry.MustRegister(authenticatedAttemptsCounter)
|
||||
}
|
||||
|
||||
// WithAuthentication creates an http handler that tries to authenticate the given request as a user, and then
|
||||
@@ -63,7 +87,11 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request, failed
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
klog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
||||
authenticatedAttemptsCounter.WithLabelValues(errorLabel).Inc()
|
||||
} else if !ok {
|
||||
authenticatedAttemptsCounter.WithLabelValues(failureLabel).Inc()
|
||||
}
|
||||
|
||||
failed.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
@@ -77,6 +105,7 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request, failed
|
||||
req = req.WithContext(genericapirequest.WithUser(req.Context(), resp.User))
|
||||
|
||||
authenticatedUserCounter.WithLabelValues(compressUsername(resp.User.GetName())).Inc()
|
||||
authenticatedAttemptsCounter.WithLabelValues(successLabel).Inc()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
|
||||
33
vendor/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol.go
generated
vendored
Normal file
33
vendor/k8s.io/apiserver/pkg/endpoints/filters/cachecontrol.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2019 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 filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// WithCacheControl sets the Cache-Control header to "no-cache, private" because all servers are protected by authn/authz.
|
||||
// see https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#defining_optimal_cache-control_policy
|
||||
func WithCacheControl(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// Set the cache-control header if it is not already set
|
||||
if _, ok := w.Header()["Cache-Control"]; !ok {
|
||||
w.Header().Set("Cache-Control", "no-cache, private")
|
||||
}
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
6
vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
generated
vendored
6
vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
generated
vendored
@@ -68,16 +68,18 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.
|
||||
groups := []string{}
|
||||
userExtra := map[string][]string{}
|
||||
for _, impersonationRequest := range impersonationRequests {
|
||||
gvk := impersonationRequest.GetObjectKind().GroupVersionKind()
|
||||
actingAsAttributes := &authorizer.AttributesRecord{
|
||||
User: requestor,
|
||||
Verb: "impersonate",
|
||||
APIGroup: impersonationRequest.GetObjectKind().GroupVersionKind().Group,
|
||||
APIGroup: gvk.Group,
|
||||
APIVersion: gvk.Version,
|
||||
Namespace: impersonationRequest.Namespace,
|
||||
Name: impersonationRequest.Name,
|
||||
ResourceRequest: true,
|
||||
}
|
||||
|
||||
switch impersonationRequest.GetObjectKind().GroupVersionKind().GroupKind() {
|
||||
switch gvk.GroupKind() {
|
||||
case v1.SchemeGroupVersion.WithKind("ServiceAccount").GroupKind():
|
||||
actingAsAttributes.Resource = "serviceaccounts"
|
||||
username = serviceaccount.MakeUsername(impersonationRequest.Namespace, impersonationRequest.Name)
|
||||
|
||||
15
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
generated
vendored
15
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
generated
vendored
@@ -20,7 +20,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
restful "github.com/emicklei/go-restful"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -72,6 +72,8 @@ type APIGroupVersion struct {
|
||||
Linker runtime.SelfLinker
|
||||
UnsafeConvertor runtime.ObjectConvertor
|
||||
|
||||
EquivalentResourceRegistry runtime.EquivalentResourceRegistry
|
||||
|
||||
// Authorizer determines whether a user is allowed to make a certain request. The Handler does a preliminary
|
||||
// authorization check using the request URI but it may be necessary to make additional checks, such as in
|
||||
// the create-on-update case
|
||||
@@ -81,10 +83,6 @@ type APIGroupVersion struct {
|
||||
|
||||
MinRequestTimeout time.Duration
|
||||
|
||||
// EnableAPIResponseCompression indicates whether API Responses should support compression
|
||||
// if the client requests it via Accept-Encoding
|
||||
EnableAPIResponseCompression bool
|
||||
|
||||
// OpenAPIModels exposes the OpenAPI models to each individual handler.
|
||||
OpenAPIModels openapiproto.Models
|
||||
|
||||
@@ -99,10 +97,9 @@ type APIGroupVersion struct {
|
||||
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
||||
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
|
||||
installer := &APIInstaller{
|
||||
group: g,
|
||||
prefix: prefix,
|
||||
minRequestTimeout: g.MinRequestTimeout,
|
||||
enableAPIResponseCompression: g.EnableAPIResponseCompression,
|
||||
group: g,
|
||||
prefix: prefix,
|
||||
minRequestTimeout: g.MinRequestTimeout,
|
||||
}
|
||||
|
||||
apiResources, ws, registrationErrors := installer.Install()
|
||||
|
||||
119
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
119
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
@@ -17,13 +17,16 @@ limitations under the License.
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
@@ -37,13 +40,13 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
|
||||
func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
// For performance tracking purposes.
|
||||
trace := utiltrace.New("Create " + req.URL.Path)
|
||||
trace := utiltrace.New("Create", utiltrace.Field{"url", req.URL.Path})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -54,29 +57,38 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||
|
||||
var (
|
||||
namespace, name string
|
||||
err error
|
||||
)
|
||||
if includeName {
|
||||
namespace, name, err = scope.Namer.Name(req)
|
||||
} else {
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
if err != nil {
|
||||
if includeName {
|
||||
// name was required, return
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise attempt to look up the namespace
|
||||
namespace, err = scope.Namer.Namespace(req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
defer cancel()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
gv := scope.Kind.GroupVersion()
|
||||
s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
decoder := scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion)
|
||||
|
||||
body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
@@ -97,6 +109,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
|
||||
|
||||
defaultGVK := scope.Kind
|
||||
original := r.New()
|
||||
@@ -119,22 +132,41 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
||||
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
|
||||
// On create, get name from new object if unset
|
||||
if len(name) == 0 {
|
||||
_, name, _ = scope.Namer.ObjectName(obj)
|
||||
}
|
||||
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
|
||||
err = mutatingAdmission.Admit(admissionAttributes)
|
||||
err = mutatingAdmission.Admit(ctx, admissionAttributes, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if scope.FieldManager != nil {
|
||||
liveObj, err := scope.Creater.New(scope.Kind)
|
||||
if err != nil {
|
||||
scope.err(fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
obj, err = scope.FieldManager.Update(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
|
||||
if err != nil {
|
||||
scope.err(fmt.Errorf("failed to update object (Create for %v) managed fields: %v", scope.Kind, err), w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
trace.Step("About to store object in database")
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
return r.Create(
|
||||
ctx,
|
||||
name,
|
||||
obj,
|
||||
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes),
|
||||
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
|
||||
options,
|
||||
)
|
||||
})
|
||||
@@ -144,41 +176,23 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
||||
}
|
||||
trace.Step("Object stored in database")
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
trace.Step("Self-link added")
|
||||
|
||||
// If the object is partially initialized, always indicate it via StatusAccepted
|
||||
code := http.StatusCreated
|
||||
if accessor, err := meta.Accessor(result); err == nil {
|
||||
if accessor.GetInitializers() != nil {
|
||||
code = http.StatusAccepted
|
||||
}
|
||||
}
|
||||
status, ok := result.(*metav1.Status)
|
||||
if ok && err == nil && status.Code == 0 {
|
||||
status.Code = int32(code)
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, code, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, code, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNamedResource returns a function that will handle a resource creation with name.
|
||||
func CreateNamedResource(r rest.NamedCreater, scope RequestScope, admission admission.Interface) http.HandlerFunc {
|
||||
func CreateNamedResource(r rest.NamedCreater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
|
||||
return createHandler(r, scope, admission, true)
|
||||
}
|
||||
|
||||
// CreateResource returns a function that will handle a resource creation.
|
||||
func CreateResource(r rest.Creater, scope RequestScope, admission admission.Interface) http.HandlerFunc {
|
||||
func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
|
||||
return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
|
||||
}
|
||||
|
||||
@@ -189,3 +203,32 @@ type namedCreaterAdapter struct {
|
||||
func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
|
||||
}
|
||||
|
||||
// manager is assumed to be already a valid value, we need to make
|
||||
// userAgent into a valid value too.
|
||||
func managerOrUserAgent(manager, userAgent string) string {
|
||||
if manager != "" {
|
||||
return manager
|
||||
}
|
||||
return prefixFromUserAgent(userAgent)
|
||||
}
|
||||
|
||||
// prefixFromUserAgent takes the characters preceding the first /, quote
|
||||
// unprintable character and then trim what's beyond the
|
||||
// FieldManagerMaxLength limit.
|
||||
func prefixFromUserAgent(u string) string {
|
||||
m := strings.Split(u, "/")[0]
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, r := range m {
|
||||
// Ignore non-printable characters
|
||||
if !unicode.IsPrint(r) {
|
||||
continue
|
||||
}
|
||||
// Only append if we have room for it
|
||||
if buf.Len()+utf8.RuneLen(r) > validation.FieldManagerMaxLength {
|
||||
break
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
106
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
106
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -35,15 +36,15 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// DeleteResource returns a function that will handle a resource deletion
|
||||
// TODO admission here becomes solely validating admission
|
||||
func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
// For performance tracking purposes.
|
||||
trace := utiltrace.New("Delete " + req.URL.Path)
|
||||
trace := utiltrace.New("Delete", utiltrace.Field{"url", req.URL.Path})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -59,11 +60,18 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
ctx := req.Context()
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
defer cancel()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
options := &metav1.DeleteOptions{}
|
||||
if allowsOptions {
|
||||
body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
@@ -107,29 +115,14 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace.Step("About to check admission control")
|
||||
if admit != nil && admit.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
if err := mutatingAdmission.Admit(attrs); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
if err := validatingAdmission.Validate(attrs); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions"))
|
||||
|
||||
trace.Step("About to delete object from database")
|
||||
wasDeleted := true
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
obj, deleted, err := r.Delete(ctx, name, options)
|
||||
obj, deleted, err := r.Delete(ctx, name, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options)
|
||||
wasDeleted = deleted
|
||||
return obj, err
|
||||
})
|
||||
@@ -160,30 +153,16 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
||||
Kind: scope.Kind.Kind,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// when a non-status response is returned, set the self link
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if _, ok := result.(*metav1.Status); !ok {
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, status, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, status, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCollection returns a function that will handle a collection deletion
|
||||
func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
trace := utiltrace.New("Delete " + req.URL.Path)
|
||||
trace := utiltrace.New("Delete", utiltrace.Field{"url", req.URL.Path})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -200,10 +179,17 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
||||
return
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
defer cancel()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
listOptions := metainternalversion.ListOptions{}
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
@@ -238,6 +224,8 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
// For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions
|
||||
// It is also allowed to pass a body with meta.k8s.io/v1.DeleteOptions
|
||||
defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions")
|
||||
obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
|
||||
if err != nil {
|
||||
@@ -264,30 +252,13 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions"))
|
||||
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
if admit != nil && admit.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
err = mutatingAdmission.Admit(attrs)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
err = validatingAdmission.Validate(attrs)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
return r.DeleteCollection(ctx, options, &listOptions)
|
||||
return r.DeleteCollection(ctx, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options, &listOptions)
|
||||
})
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
@@ -304,17 +275,8 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
||||
Kind: scope.Kind.Kind,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// when a non-status response is returned, set the self link
|
||||
if _, ok := result.(*metav1.Status); !ok {
|
||||
if _, err := setListSelfLink(result, ctx, req, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
5
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/OWNERS
generated
vendored
Normal file
5
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
approvers:
|
||||
- jennybuckley
|
||||
- apelisse
|
||||
reviewers:
|
||||
- kwiesmueller
|
||||
331
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
Normal file
331
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
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 fieldmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"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/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||
"k8s.io/klog"
|
||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/merge"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// FieldManager updates the managed fields and merge applied
|
||||
// configurations.
|
||||
type FieldManager struct {
|
||||
typeConverter internal.TypeConverter
|
||||
objectConverter runtime.ObjectConvertor
|
||||
objectDefaulter runtime.ObjectDefaulter
|
||||
groupVersion schema.GroupVersion
|
||||
hubVersion schema.GroupVersion
|
||||
updater merge.Updater
|
||||
}
|
||||
|
||||
// NewFieldManager creates a new FieldManager that merges apply requests
|
||||
// and update managed fields for other types of requests.
|
||||
func NewFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (*FieldManager, error) {
|
||||
typeConverter, err := internal.NewTypeConverter(models, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FieldManager{
|
||||
typeConverter: typeConverter,
|
||||
objectConverter: objectConverter,
|
||||
objectDefaulter: objectDefaulter,
|
||||
groupVersion: gv,
|
||||
hubVersion: hub,
|
||||
updater: merge.Updater{
|
||||
Converter: internal.NewVersionConverter(typeConverter, objectConverter, hub),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewCRDFieldManager creates a new FieldManager specifically for
|
||||
// CRDs. This allows for the possibility of fields which are not defined
|
||||
// in models, as well as having no models defined at all.
|
||||
func NewCRDFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, preserveUnknownFields bool) (_ *FieldManager, err error) {
|
||||
var typeConverter internal.TypeConverter = internal.DeducedTypeConverter{}
|
||||
if models != nil {
|
||||
typeConverter, err = internal.NewTypeConverter(models, preserveUnknownFields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &FieldManager{
|
||||
typeConverter: typeConverter,
|
||||
objectConverter: objectConverter,
|
||||
objectDefaulter: objectDefaulter,
|
||||
groupVersion: gv,
|
||||
hubVersion: hub,
|
||||
updater: merge.Updater{
|
||||
Converter: internal.NewCRDVersionConverter(typeConverter, objectConverter, hub),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Update is used when the object has already been merged (non-apply
|
||||
// use-case), and simply updates the managed fields in the output
|
||||
// object.
|
||||
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) {
|
||||
// If the object doesn't have metadata, we should just return without trying to
|
||||
// set the managedFields at all, so creates/updates/patches will work normally.
|
||||
if _, err := meta.Accessor(newObj); err != nil {
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// First try to decode the managed fields provided in the update,
|
||||
// This is necessary to allow directly updating managed fields.
|
||||
managed, err := internal.DecodeObjectManagedFields(newObj)
|
||||
|
||||
// If the managed field is empty or we failed to decode it,
|
||||
// let's try the live object. This is to prevent clients who
|
||||
// don't understand managedFields from deleting it accidentally.
|
||||
if err != nil || len(managed.Fields) == 0 {
|
||||
managed, err = internal.DecodeObjectManagedFields(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||
}
|
||||
}
|
||||
// if managed field is still empty, skip updating managed fields altogether
|
||||
if len(managed.Fields) == 0 {
|
||||
return newObj, nil
|
||||
}
|
||||
newObjVersioned, err := f.toVersioned(newObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
||||
}
|
||||
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||
}
|
||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
||||
internal.RemoveObjectManagedFields(newObjVersioned)
|
||||
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
||||
if err != nil {
|
||||
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
||||
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed new object: %v", err)
|
||||
return newObj, nil
|
||||
}
|
||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||
if err != nil {
|
||||
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
||||
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed live object: %v", err)
|
||||
return newObj, nil
|
||||
}
|
||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||
|
||||
// TODO(apelisse) use the first return value when unions are implemented
|
||||
_, managed.Fields, err = f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields, manager)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update ManagedFields: %v", err)
|
||||
}
|
||||
managed.Fields = f.stripFields(managed.Fields, manager)
|
||||
|
||||
// If the current operation took any fields from anything, it means the object changed,
|
||||
// so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager
|
||||
if vs, ok := managed.Fields[manager]; ok {
|
||||
delete(managed.Fields, manager)
|
||||
|
||||
// Build a manager identifier which will only match previous updates from the same manager
|
||||
manager, err = f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||
}
|
||||
|
||||
managed.Times[manager] = &metav1.Time{Time: time.Now().UTC()}
|
||||
if previous, ok := managed.Fields[manager]; ok {
|
||||
managed.Fields[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied())
|
||||
} else {
|
||||
managed.Fields[manager] = vs
|
||||
}
|
||||
}
|
||||
|
||||
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
|
||||
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// Apply is used when server-side apply is called, as it merges the
|
||||
// object and update the managed fields.
|
||||
func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager string, force bool) (runtime.Object, error) {
|
||||
// If the object doesn't have metadata, apply isn't allowed.
|
||||
accessor, err := meta.Accessor(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get accessor: %v", err)
|
||||
}
|
||||
missingManagedFields := (len(accessor.GetManagedFields()) == 0)
|
||||
|
||||
managed, err := internal.DecodeObjectManagedFields(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||
}
|
||||
// Check that the patch object has the same version as the live object
|
||||
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
|
||||
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err))
|
||||
}
|
||||
|
||||
if patchObj.GetManagedFields() != nil {
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("metadata.managedFields must be nil"))
|
||||
}
|
||||
|
||||
if patchObj.GetAPIVersion() != f.groupVersion.String() {
|
||||
return nil,
|
||||
errors.NewBadRequest(
|
||||
fmt.Sprintf("Incorrect version specified in apply patch. "+
|
||||
"Specified patch version: %s, expected: %s",
|
||||
patchObj.GetAPIVersion(), f.groupVersion.String()))
|
||||
}
|
||||
|
||||
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||
}
|
||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
||||
|
||||
patchObjTyped, err := f.typeConverter.YAMLToTyped(patch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed patch object: %v", err)
|
||||
}
|
||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
||||
}
|
||||
manager, err := f.buildManagerInfo(fieldManager, metav1.ManagedFieldsOperationApply)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||
}
|
||||
|
||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||
// if managed field is missing, create a single entry for all the fields
|
||||
if missingManagedFields {
|
||||
unknownManager, err := internal.BuildManagerIdentifier(&metav1.ManagedFieldsEntry{
|
||||
Manager: "before-first-apply",
|
||||
Operation: metav1.ManagedFieldsOperationUpdate,
|
||||
APIVersion: f.groupVersion.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create manager for existing fields: %v", err)
|
||||
}
|
||||
unknownFieldSet, err := liveObjTyped.ToFieldSet()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create fieldset for existing fields: %v", err)
|
||||
}
|
||||
managed.Fields[unknownManager] = fieldpath.NewVersionedSet(unknownFieldSet, apiVersion, false)
|
||||
f.stripFields(managed.Fields, unknownManager)
|
||||
}
|
||||
newObjTyped, managedFields, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed.Fields, manager, force)
|
||||
if err != nil {
|
||||
if conflicts, ok := err.(merge.Conflicts); ok {
|
||||
return nil, internal.NewConflictError(conflicts)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
managed.Fields = f.stripFields(managedFields, manager)
|
||||
|
||||
// Update the time in the managedFieldsEntry for this operation
|
||||
managed.Times[manager] = &metav1.Time{Time: time.Now().UTC()}
|
||||
|
||||
newObj, err := f.typeConverter.TypedToObject(newObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert new typed object to object: %v", err)
|
||||
}
|
||||
|
||||
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
|
||||
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||
}
|
||||
|
||||
newObjVersioned, err := f.toVersioned(newObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
||||
}
|
||||
f.objectDefaulter.Default(newObjVersioned)
|
||||
|
||||
newObjUnversioned, err := f.toUnversioned(newObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to unversioned: %v", err)
|
||||
}
|
||||
return newObjUnversioned, nil
|
||||
}
|
||||
|
||||
func (f *FieldManager) toVersioned(obj runtime.Object) (runtime.Object, error) {
|
||||
return f.objectConverter.ConvertToVersion(obj, f.groupVersion)
|
||||
}
|
||||
|
||||
func (f *FieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
|
||||
return f.objectConverter.ConvertToVersion(obj, f.hubVersion)
|
||||
}
|
||||
|
||||
func (f *FieldManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) {
|
||||
managerInfo := metav1.ManagedFieldsEntry{
|
||||
Manager: prefix,
|
||||
Operation: operation,
|
||||
APIVersion: f.groupVersion.String(),
|
||||
}
|
||||
if managerInfo.Manager == "" {
|
||||
managerInfo.Manager = "unknown"
|
||||
}
|
||||
return internal.BuildManagerIdentifier(&managerInfo)
|
||||
}
|
||||
|
||||
// stripSet is the list of fields that should never be part of a mangedFields.
|
||||
var stripSet = fieldpath.NewSet(
|
||||
fieldpath.MakePathOrDie("apiVersion"),
|
||||
fieldpath.MakePathOrDie("kind"),
|
||||
fieldpath.MakePathOrDie("metadata"),
|
||||
fieldpath.MakePathOrDie("metadata", "name"),
|
||||
fieldpath.MakePathOrDie("metadata", "namespace"),
|
||||
fieldpath.MakePathOrDie("metadata", "creationTimestamp"),
|
||||
fieldpath.MakePathOrDie("metadata", "selfLink"),
|
||||
fieldpath.MakePathOrDie("metadata", "uid"),
|
||||
fieldpath.MakePathOrDie("metadata", "clusterName"),
|
||||
fieldpath.MakePathOrDie("metadata", "generation"),
|
||||
fieldpath.MakePathOrDie("metadata", "managedFields"),
|
||||
fieldpath.MakePathOrDie("metadata", "resourceVersion"),
|
||||
)
|
||||
|
||||
// stripFields removes a predefined set of paths found in typed from managed and returns the updated ManagedFields
|
||||
func (f *FieldManager) stripFields(managed fieldpath.ManagedFields, manager string) fieldpath.ManagedFields {
|
||||
vs, ok := managed[manager]
|
||||
if ok {
|
||||
if vs == nil {
|
||||
panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager))
|
||||
}
|
||||
newSet := vs.Set().Difference(stripSet)
|
||||
if newSet.Empty() {
|
||||
delete(managed, manager)
|
||||
} else {
|
||||
managed[manager] = fieldpath.NewVersionedSet(newSet, vs.APIVersion(), vs.Applied())
|
||||
}
|
||||
}
|
||||
|
||||
return managed
|
||||
}
|
||||
85
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
Normal file
85
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2019 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 internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/merge"
|
||||
)
|
||||
|
||||
// NewConflictError returns an error including details on the requests apply conflicts
|
||||
func NewConflictError(conflicts merge.Conflicts) *errors.StatusError {
|
||||
causes := []metav1.StatusCause{}
|
||||
for _, conflict := range conflicts {
|
||||
causes = append(causes, metav1.StatusCause{
|
||||
Type: metav1.CauseTypeFieldManagerConflict,
|
||||
Message: fmt.Sprintf("conflict with %v", printManager(conflict.Manager)),
|
||||
Field: conflict.Path.String(),
|
||||
})
|
||||
}
|
||||
return errors.NewApplyConflict(causes, getConflictMessage(conflicts))
|
||||
}
|
||||
|
||||
func getConflictMessage(conflicts merge.Conflicts) string {
|
||||
if len(conflicts) == 1 {
|
||||
return fmt.Sprintf("Apply failed with 1 conflict: conflict with %v: %v", printManager(conflicts[0].Manager), conflicts[0].Path)
|
||||
}
|
||||
|
||||
m := map[string][]fieldpath.Path{}
|
||||
for _, conflict := range conflicts {
|
||||
m[conflict.Manager] = append(m[conflict.Manager], conflict.Path)
|
||||
}
|
||||
|
||||
uniqueManagers := []string{}
|
||||
for manager := range m {
|
||||
uniqueManagers = append(uniqueManagers, manager)
|
||||
}
|
||||
|
||||
// Print conflicts by sorted managers.
|
||||
sort.Strings(uniqueManagers)
|
||||
|
||||
messages := []string{}
|
||||
for _, manager := range uniqueManagers {
|
||||
messages = append(messages, fmt.Sprintf("conflicts with %v:", printManager(manager)))
|
||||
for _, path := range m[manager] {
|
||||
messages = append(messages, fmt.Sprintf("- %v", path))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), strings.Join(messages, "\n"))
|
||||
}
|
||||
|
||||
func printManager(manager string) string {
|
||||
encodedManager := &metav1.ManagedFieldsEntry{}
|
||||
if err := json.Unmarshal([]byte(manager), encodedManager); err != nil {
|
||||
return fmt.Sprintf("%q", manager)
|
||||
}
|
||||
if encodedManager.Operation == metav1.ManagedFieldsOperationUpdate {
|
||||
if encodedManager.Time == nil {
|
||||
return fmt.Sprintf("%q using %v", encodedManager.Manager, encodedManager.APIVersion)
|
||||
}
|
||||
return fmt.Sprintf("%q using %v at %v", encodedManager.Manager, encodedManager.APIVersion, encodedManager.Time.UTC().Format(time.RFC3339))
|
||||
}
|
||||
return fmt.Sprintf("%q", encodedManager.Manager)
|
||||
}
|
||||
47
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
Normal file
47
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
)
|
||||
|
||||
// EmptyFields represents a set with no paths
|
||||
// It looks like metav1.Fields{Raw: []byte("{}")}
|
||||
var EmptyFields metav1.FieldsV1 = func() metav1.FieldsV1 {
|
||||
f, err := SetToFields(*fieldpath.NewSet())
|
||||
if err != nil {
|
||||
panic("should never happen")
|
||||
}
|
||||
return f
|
||||
}()
|
||||
|
||||
// FieldsToSet creates a set paths from an input trie of fields
|
||||
func FieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) {
|
||||
err = s.FromJSON(bytes.NewReader(f.Raw))
|
||||
return s, err
|
||||
}
|
||||
|
||||
// SetToFields creates a trie of fields from an input set of paths
|
||||
func SetToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) {
|
||||
f.Raw, err = s.ToJSON()
|
||||
return f, err
|
||||
}
|
||||
121
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go
generated
vendored
Normal file
121
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kube-openapi/pkg/schemaconv"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/typed"
|
||||
)
|
||||
|
||||
// groupVersionKindExtensionKey is the key used to lookup the
|
||||
// GroupVersionKind value for an object definition from the
|
||||
// definition's "extensions" map.
|
||||
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
||||
|
||||
type gvkParser struct {
|
||||
gvks map[schema.GroupVersionKind]string
|
||||
parser typed.Parser
|
||||
}
|
||||
|
||||
func (p *gvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType {
|
||||
typeName, ok := p.gvks[gvk]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
t := p.parser.Type(typeName)
|
||||
return &t
|
||||
}
|
||||
|
||||
func newGVKParser(models proto.Models, preserveUnknownFields bool) (*gvkParser, error) {
|
||||
typeSchema, err := schemaconv.ToSchemaWithPreserveUnknownFields(models, preserveUnknownFields)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert models to schema: %v", err)
|
||||
}
|
||||
parser := gvkParser{
|
||||
gvks: map[schema.GroupVersionKind]string{},
|
||||
}
|
||||
parser.parser = typed.Parser{Schema: *typeSchema}
|
||||
for _, modelName := range models.ListModels() {
|
||||
model := models.LookupModel(modelName)
|
||||
if model == nil {
|
||||
panic(fmt.Sprintf("ListModels returns a model that can't be looked-up for: %v", modelName))
|
||||
}
|
||||
gvkList := parseGroupVersionKind(model)
|
||||
for _, gvk := range gvkList {
|
||||
if len(gvk.Kind) > 0 {
|
||||
_, ok := parser.gvks[gvk]
|
||||
if ok {
|
||||
return nil, fmt.Errorf("duplicate entry for %v", gvk)
|
||||
}
|
||||
parser.gvks[gvk] = modelName
|
||||
}
|
||||
}
|
||||
}
|
||||
return &parser, nil
|
||||
}
|
||||
|
||||
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
|
||||
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
|
||||
extensions := s.GetExtensions()
|
||||
|
||||
gvkListResult := []schema.GroupVersionKind{}
|
||||
|
||||
// Get the extensions
|
||||
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
// gvk extension must be a list of at least 1 element.
|
||||
gvkList, ok := gvkExtension.([]interface{})
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
for _, gvk := range gvkList {
|
||||
// gvk extension list must be a map with group, version, and
|
||||
// kind fields
|
||||
gvkMap, ok := gvk.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
group, ok := gvkMap["group"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version, ok := gvkMap["version"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
kind, ok := gvkMap["kind"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
|
||||
Group: group,
|
||||
Version: version,
|
||||
Kind: kind,
|
||||
})
|
||||
}
|
||||
|
||||
return gvkListResult
|
||||
}
|
||||
205
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
Normal file
205
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
)
|
||||
|
||||
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
||||
type Managed struct {
|
||||
Fields fieldpath.ManagedFields
|
||||
Times map[string]*metav1.Time
|
||||
}
|
||||
|
||||
// RemoveObjectManagedFields removes the ManagedFields from the object
|
||||
// before we merge so that it doesn't appear in the ManagedFields
|
||||
// recursively.
|
||||
func RemoveObjectManagedFields(obj runtime.Object) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
accessor.SetManagedFields(nil)
|
||||
}
|
||||
|
||||
// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields.
|
||||
func DecodeObjectManagedFields(from runtime.Object) (Managed, error) {
|
||||
if from == nil {
|
||||
return Managed{}, nil
|
||||
}
|
||||
accessor, err := meta.Accessor(from)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
|
||||
managed, err := decodeManagedFields(accessor.GetManagedFields())
|
||||
if err != nil {
|
||||
return Managed{}, fmt.Errorf("failed to convert managed fields from API: %v", err)
|
||||
}
|
||||
return managed, err
|
||||
}
|
||||
|
||||
// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields
|
||||
func EncodeObjectManagedFields(obj runtime.Object, managed Managed) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
|
||||
encodedManagedFields, err := encodeManagedFields(managed)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert back managed fields to API: %v", err)
|
||||
}
|
||||
accessor.SetManagedFields(encodedManagedFields)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeManagedFields converts ManagedFields from the wire format (api format)
|
||||
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managed Managed, err error) {
|
||||
managed.Fields = make(fieldpath.ManagedFields, len(encodedManagedFields))
|
||||
managed.Times = make(map[string]*metav1.Time, len(encodedManagedFields))
|
||||
for _, encodedVersionedSet := range encodedManagedFields {
|
||||
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
|
||||
if err != nil {
|
||||
return Managed{}, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
||||
}
|
||||
managed.Fields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
||||
if err != nil {
|
||||
return Managed{}, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
||||
}
|
||||
managed.Times[manager] = encodedVersionedSet.Time
|
||||
}
|
||||
return managed, nil
|
||||
}
|
||||
|
||||
// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry
|
||||
func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) {
|
||||
encodedManagerCopy := *encodedManager
|
||||
|
||||
// Never include fields type in the manager identifier
|
||||
encodedManagerCopy.FieldsType = ""
|
||||
|
||||
// Never include the fields in the manager identifier
|
||||
encodedManagerCopy.FieldsV1 = nil
|
||||
|
||||
// Never include the time in the manager identifier
|
||||
encodedManagerCopy.Time = nil
|
||||
|
||||
// For appliers, don't include the APIVersion in the manager identifier,
|
||||
// so it will always have the same manager identifier each time it applied.
|
||||
if encodedManager.Operation == metav1.ManagedFieldsOperationApply {
|
||||
encodedManagerCopy.APIVersion = ""
|
||||
}
|
||||
|
||||
// Use the remaining fields to build the manager identifier
|
||||
b, err := json.Marshal(&encodedManagerCopy)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error marshalling manager identifier: %v", err)
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet fieldpath.VersionedSet, err error) {
|
||||
fields := EmptyFields
|
||||
if encodedVersionedSet.FieldsV1 != nil {
|
||||
fields = *encodedVersionedSet.FieldsV1
|
||||
}
|
||||
set, err := FieldsToSet(fields)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding set: %v", err)
|
||||
}
|
||||
return fieldpath.NewVersionedSet(&set, fieldpath.APIVersion(encodedVersionedSet.APIVersion), encodedVersionedSet.Operation == metav1.ManagedFieldsOperationApply), nil
|
||||
}
|
||||
|
||||
// encodeManagedFields converts ManagedFields from the format used by
|
||||
// sigs.k8s.io/structured-merge-diff to the wire format (api format)
|
||||
func encodeManagedFields(managed Managed) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||
encodedManagedFields = []metav1.ManagedFieldsEntry{}
|
||||
for manager := range managed.Fields {
|
||||
versionedSet := managed.Fields[manager]
|
||||
v, err := encodeManagerVersionedSet(manager, versionedSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
|
||||
}
|
||||
if t, ok := managed.Times[manager]; ok {
|
||||
v.Time = t
|
||||
}
|
||||
encodedManagedFields = append(encodedManagedFields, *v)
|
||||
}
|
||||
return sortEncodedManagedFields(encodedManagedFields)
|
||||
}
|
||||
|
||||
func sortEncodedManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (sortedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||
sort.Slice(encodedManagedFields, func(i, j int) bool {
|
||||
p, q := encodedManagedFields[i], encodedManagedFields[j]
|
||||
|
||||
if p.Operation != q.Operation {
|
||||
return p.Operation < q.Operation
|
||||
}
|
||||
|
||||
ntime := &metav1.Time{Time: time.Time{}}
|
||||
if p.Time == nil {
|
||||
p.Time = ntime
|
||||
}
|
||||
if q.Time == nil {
|
||||
q.Time = ntime
|
||||
}
|
||||
if !p.Time.Equal(q.Time) {
|
||||
return p.Time.Before(q.Time)
|
||||
}
|
||||
|
||||
return p.Manager < q.Manager
|
||||
})
|
||||
|
||||
return encodedManagedFields, nil
|
||||
}
|
||||
|
||||
func encodeManagerVersionedSet(manager string, versionedSet fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) {
|
||||
encodedVersionedSet = &metav1.ManagedFieldsEntry{}
|
||||
|
||||
// Get as many fields as we can from the manager identifier
|
||||
err = json.Unmarshal([]byte(manager), encodedVersionedSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", manager, err)
|
||||
}
|
||||
|
||||
// Get the APIVersion, Operation, and Fields from the VersionedSet
|
||||
encodedVersionedSet.APIVersion = string(versionedSet.APIVersion())
|
||||
if versionedSet.Applied() {
|
||||
encodedVersionedSet.Operation = metav1.ManagedFieldsOperationApply
|
||||
}
|
||||
encodedVersionedSet.FieldsType = "FieldsV1"
|
||||
fields, err := SetToFields(*versionedSet.Set())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error encoding set: %v", err)
|
||||
}
|
||||
encodedVersionedSet.FieldsV1 = &fields
|
||||
|
||||
return encodedVersionedSet, nil
|
||||
}
|
||||
140
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
Normal file
140
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/value"
|
||||
)
|
||||
|
||||
const (
|
||||
// Field indicates that the content of this path element is a field's name
|
||||
Field = "f"
|
||||
|
||||
// Value indicates that the content of this path element is a field's value
|
||||
Value = "v"
|
||||
|
||||
// Index indicates that the content of this path element is an index in an array
|
||||
Index = "i"
|
||||
|
||||
// Key indicates that the content of this path element is a key value map
|
||||
Key = "k"
|
||||
|
||||
// Separator separates the type of a path element from the contents
|
||||
Separator = ":"
|
||||
)
|
||||
|
||||
// NewPathElement parses a serialized path element
|
||||
func NewPathElement(s string) (fieldpath.PathElement, error) {
|
||||
split := strings.SplitN(s, Separator, 2)
|
||||
if len(split) < 2 {
|
||||
return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s)
|
||||
}
|
||||
switch split[0] {
|
||||
case Field:
|
||||
return fieldpath.PathElement{
|
||||
FieldName: &split[1],
|
||||
}, nil
|
||||
case Value:
|
||||
val, err := value.FromJSON([]byte(split[1]))
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
return fieldpath.PathElement{
|
||||
Value: &val,
|
||||
}, nil
|
||||
case Index:
|
||||
i, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
return fieldpath.PathElement{
|
||||
Index: &i,
|
||||
}, nil
|
||||
case Key:
|
||||
kv := map[string]json.RawMessage{}
|
||||
err := json.Unmarshal([]byte(split[1]), &kv)
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
fields := []value.Field{}
|
||||
for k, v := range kv {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
val, err := value.FromJSON(b)
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
|
||||
fields = append(fields, value.Field{
|
||||
Name: k,
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
return fieldpath.PathElement{
|
||||
Key: &value.Map{Items: fields},
|
||||
}, nil
|
||||
default:
|
||||
// Ignore unknown key types
|
||||
return fieldpath.PathElement{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// PathElementString serializes a path element
|
||||
func PathElementString(pe fieldpath.PathElement) (string, error) {
|
||||
switch {
|
||||
case pe.FieldName != nil:
|
||||
return Field + Separator + *pe.FieldName, nil
|
||||
case pe.Key != nil:
|
||||
kv := map[string]json.RawMessage{}
|
||||
for _, k := range pe.Key.Items {
|
||||
b, err := k.Value.ToJSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := json.RawMessage{}
|
||||
err = json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
kv[k.Name] = m
|
||||
}
|
||||
b, err := json.Marshal(kv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Key + ":" + string(b), nil
|
||||
case pe.Value != nil:
|
||||
b, err := pe.Value.ToJSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Value + ":" + string(b), nil
|
||||
case pe.Index != nil:
|
||||
return Index + ":" + strconv.Itoa(*pe.Index), nil
|
||||
default:
|
||||
return "", errors.New("Invalid type of path element")
|
||||
}
|
||||
}
|
||||
148
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go
generated
vendored
Normal file
148
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/value"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// TypeConverter allows you to convert from runtime.Object to
|
||||
// typed.TypedValue and the other way around.
|
||||
type TypeConverter interface {
|
||||
ObjectToTyped(runtime.Object) (*typed.TypedValue, error)
|
||||
YAMLToTyped([]byte) (*typed.TypedValue, error)
|
||||
TypedToObject(*typed.TypedValue) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// DeducedTypeConverter is a TypeConverter for CRDs that don't have a
|
||||
// schema. It does implement the same interface though (and create the
|
||||
// same types of objects), so that everything can still work the same.
|
||||
// CRDs are merged with all their fields being "atomic" (lists
|
||||
// included).
|
||||
//
|
||||
// Note that this is not going to be sufficient for converting to/from
|
||||
// CRDs that have a schema defined (we don't support that schema yet).
|
||||
// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
|
||||
type DeducedTypeConverter struct{}
|
||||
|
||||
var _ TypeConverter = DeducedTypeConverter{}
|
||||
|
||||
// ObjectToTyped converts an object into a TypedValue with a "deduced type".
|
||||
func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
|
||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return typed.DeducedParseableType.FromUnstructured(u)
|
||||
}
|
||||
|
||||
// YAMLToTyped parses a yaml object into a TypedValue with a "deduced type".
|
||||
func (DeducedTypeConverter) YAMLToTyped(from []byte) (*typed.TypedValue, error) {
|
||||
return typed.DeducedParseableType.FromYAML(typed.YAMLObject(from))
|
||||
}
|
||||
|
||||
// TypedToObject transforms the typed value into a runtime.Object. That
|
||||
// is not specific to deduced type.
|
||||
func (DeducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
|
||||
return valueToObject(value.AsValue())
|
||||
}
|
||||
|
||||
type typeConverter struct {
|
||||
parser *gvkParser
|
||||
}
|
||||
|
||||
var _ TypeConverter = &typeConverter{}
|
||||
|
||||
// NewTypeConverter builds a TypeConverter from a proto.Models. This
|
||||
// will automatically find the proper version of the object, and the
|
||||
// corresponding schema information.
|
||||
func NewTypeConverter(models proto.Models, preserveUnknownFields bool) (TypeConverter, error) {
|
||||
parser, err := newGVKParser(models, preserveUnknownFields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &typeConverter{parser: parser}, nil
|
||||
}
|
||||
|
||||
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
|
||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
t := c.parser.Type(gvk)
|
||||
if t == nil {
|
||||
return nil, newNoCorrespondingTypeError(gvk)
|
||||
}
|
||||
return t.FromUnstructured(u)
|
||||
}
|
||||
|
||||
func (c *typeConverter) YAMLToTyped(from []byte) (*typed.TypedValue, error) {
|
||||
unstructured := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
|
||||
if err := yaml.Unmarshal(from, &unstructured.Object); err != nil {
|
||||
return nil, fmt.Errorf("error decoding YAML: %v", err)
|
||||
}
|
||||
|
||||
gvk := unstructured.GetObjectKind().GroupVersionKind()
|
||||
t := c.parser.Type(gvk)
|
||||
if t == nil {
|
||||
return nil, newNoCorrespondingTypeError(gvk)
|
||||
}
|
||||
return t.FromYAML(typed.YAMLObject(string(from)))
|
||||
}
|
||||
|
||||
func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
|
||||
return valueToObject(value.AsValue())
|
||||
}
|
||||
|
||||
func valueToObject(value *value.Value) (runtime.Object, error) {
|
||||
vu := value.ToUnstructured(false)
|
||||
u, ok := vu.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert typed to unstructured: want map, got %T", vu)
|
||||
}
|
||||
return &unstructured.Unstructured{Object: u}, nil
|
||||
}
|
||||
|
||||
type noCorrespondingTypeErr struct {
|
||||
gvk schema.GroupVersionKind
|
||||
}
|
||||
|
||||
func newNoCorrespondingTypeError(gvk schema.GroupVersionKind) error {
|
||||
return &noCorrespondingTypeErr{gvk: gvk}
|
||||
}
|
||||
|
||||
func (k *noCorrespondingTypeErr) Error() string {
|
||||
return fmt.Sprintf("no corresponding type for %v", k.gvk)
|
||||
}
|
||||
|
||||
func isNoCorrespondingTypeError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := err.(*noCorrespondingTypeErr)
|
||||
return ok
|
||||
}
|
||||
101
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go
generated
vendored
Normal file
101
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
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 internal
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/merge"
|
||||
"sigs.k8s.io/structured-merge-diff/typed"
|
||||
)
|
||||
|
||||
// versionConverter is an implementation of
|
||||
// sigs.k8s.io/structured-merge-diff/merge.Converter
|
||||
type versionConverter struct {
|
||||
typeConverter TypeConverter
|
||||
objectConvertor runtime.ObjectConvertor
|
||||
hubGetter func(from schema.GroupVersion) schema.GroupVersion
|
||||
}
|
||||
|
||||
var _ merge.Converter = &versionConverter{}
|
||||
|
||||
// NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor.
|
||||
func NewVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
return &versionConverter{
|
||||
typeConverter: t,
|
||||
objectConvertor: o,
|
||||
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
|
||||
return schema.GroupVersion{
|
||||
Group: from.Group,
|
||||
Version: h.Version,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewCRDVersionConverter builds a VersionConverter for CRDs from a TypeConverter and an ObjectConvertor.
|
||||
func NewCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
return &versionConverter{
|
||||
typeConverter: t,
|
||||
objectConvertor: o,
|
||||
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
|
||||
return h
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert implements sigs.k8s.io/structured-merge-diff/merge.Converter
|
||||
func (v *versionConverter) Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
|
||||
// Convert the smd typed value to a kubernetes object.
|
||||
objectToConvert, err := v.typeConverter.TypedToObject(object)
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// Parse the target groupVersion.
|
||||
groupVersion, err := schema.ParseGroupVersion(string(version))
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// If attempting to convert to the same version as we already have, just return it.
|
||||
fromVersion := objectToConvert.GetObjectKind().GroupVersionKind().GroupVersion()
|
||||
if fromVersion == groupVersion {
|
||||
return object, nil
|
||||
}
|
||||
|
||||
// Convert to internal
|
||||
internalObject, err := v.objectConvertor.ConvertToVersion(objectToConvert, v.hubGetter(fromVersion))
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// Convert the object into the target version
|
||||
convertedObject, err := v.objectConvertor.ConvertToVersion(internalObject, groupVersion)
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// Convert the object back to a smd typed value and return it.
|
||||
return v.typeConverter.ObjectToTyped(convertedObject)
|
||||
}
|
||||
|
||||
// IsMissingVersionError
|
||||
func (v *versionConverter) IsMissingVersionError(err error) bool {
|
||||
return runtime.IsNotRegisteredError(err) || isNoCorrespondingTypeError(err)
|
||||
}
|
||||
66
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
66
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
@@ -33,10 +33,11 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// getterFunc performs a get request with the given context and object name. The request
|
||||
@@ -45,9 +46,9 @@ type getterFunc func(ctx context.Context, name string, req *http.Request, trace
|
||||
|
||||
// getResourceHandler is an HTTP handler function for get requests. It delegates to the
|
||||
// passed-in getterFunc to perform the actual get.
|
||||
func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc {
|
||||
func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
trace := utiltrace.New("Get " + req.URL.Path)
|
||||
trace := utiltrace.New("Get", utiltrace.Field{"url", req.URL.Path})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
@@ -58,30 +59,26 @@ func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc
|
||||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := getter(ctx, name, req, trace)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace.Step("About to write a response")
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
|
||||
trace.Step("Transformed response object")
|
||||
}
|
||||
}
|
||||
|
||||
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
|
||||
func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) http.HandlerFunc {
|
||||
func GetResource(r rest.Getter, e rest.Exporter, scope *RequestScope) http.HandlerFunc {
|
||||
return getResourceHandler(scope,
|
||||
func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) {
|
||||
// check for export
|
||||
@@ -111,7 +108,7 @@ func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) http.Handle
|
||||
}
|
||||
|
||||
// GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object.
|
||||
func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope, isSubresource bool) http.HandlerFunc {
|
||||
func GetResourceWithOptions(r rest.GetterWithOptions, scope *RequestScope, isSubresource bool) http.HandlerFunc {
|
||||
return getResourceHandler(scope,
|
||||
func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) {
|
||||
opts, subpath, subpathKey := r.NewGetOptions()
|
||||
@@ -128,7 +125,7 @@ func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope, isSubr
|
||||
}
|
||||
|
||||
// getRequestOptions parses out options and can include path information. The path information shouldn't include the subresource.
|
||||
func getRequestOptions(req *http.Request, scope RequestScope, into runtime.Object, subpath bool, subpathKey string, isSubresource bool) error {
|
||||
func getRequestOptions(req *http.Request, scope *RequestScope, into runtime.Object, subpath bool, subpathKey string, isSubresource bool) error {
|
||||
if into == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -165,10 +162,10 @@ func getRequestOptions(req *http.Request, scope RequestScope, into runtime.Objec
|
||||
return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into)
|
||||
}
|
||||
|
||||
func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc {
|
||||
func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
// For performance tracking purposes.
|
||||
trace := utiltrace.New("List " + req.URL.Path)
|
||||
trace := utiltrace.New("List", utiltrace.Field{"url", req.URL.Path})
|
||||
|
||||
namespace, err := scope.Namer.Namespace(req)
|
||||
if err != nil {
|
||||
@@ -187,6 +184,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
||||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
opts := metainternalversion.ListOptions{}
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
@@ -245,15 +248,16 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
||||
timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0))
|
||||
}
|
||||
klog.V(3).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout)
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
watcher, err := rw.Watch(ctx, &opts)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||
serveWatch(watcher, scope, req, w, timeout)
|
||||
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
|
||||
serveWatch(watcher, scope, outputMediaType, req, w, timeout)
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -267,22 +271,8 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
||||
return
|
||||
}
|
||||
trace.Step("Listing from storage done")
|
||||
numberOfItems, err := setListSelfLink(result, ctx, req, scope.Namer)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
trace.Step("Self-linking done")
|
||||
// Ensure empty lists return a non-nil items slice
|
||||
if numberOfItems == 0 && meta.IsListType(result) {
|
||||
if err := meta.SetList(result, []runtime.Object{}); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems))
|
||||
transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
|
||||
trace.Step("Writing http response done", utiltrace.Field{"count", meta.LenList(result)})
|
||||
}
|
||||
}
|
||||
|
||||
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/namer.go
generated
vendored
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/namer.go
generated
vendored
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -86,6 +87,21 @@ func (n ContextBasedNaming) Name(req *http.Request) (namespace, name string, err
|
||||
return ns, requestInfo.Name, nil
|
||||
}
|
||||
|
||||
// fastURLPathEncode encodes the provided path as a URL path
|
||||
func fastURLPathEncode(path string) string {
|
||||
for _, r := range []byte(path) {
|
||||
switch {
|
||||
case r >= '-' && r <= '9', r >= 'A' && r <= 'Z', r >= 'a' && r <= 'z':
|
||||
// characters within this range do not require escaping
|
||||
default:
|
||||
var u url.URL
|
||||
u.Path = path
|
||||
return u.EscapedPath()
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) {
|
||||
namespace, name, err := n.ObjectName(obj)
|
||||
if err == errEmptyName && len(requestInfo.Name) > 0 {
|
||||
@@ -101,19 +117,23 @@ func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj r
|
||||
return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil
|
||||
}
|
||||
|
||||
return n.SelfLinkPathPrefix +
|
||||
url.QueryEscape(namespace) +
|
||||
"/" + url.QueryEscape(requestInfo.Resource) + "/" +
|
||||
url.QueryEscape(name) +
|
||||
n.SelfLinkPathSuffix,
|
||||
nil
|
||||
builder := strings.Builder{}
|
||||
builder.Grow(len(n.SelfLinkPathPrefix) + len(namespace) + len(requestInfo.Resource) + len(name) + len(n.SelfLinkPathSuffix) + 8)
|
||||
builder.WriteString(n.SelfLinkPathPrefix)
|
||||
builder.WriteString(namespace)
|
||||
builder.WriteByte('/')
|
||||
builder.WriteString(requestInfo.Resource)
|
||||
builder.WriteByte('/')
|
||||
builder.WriteString(name)
|
||||
builder.WriteString(n.SelfLinkPathSuffix)
|
||||
return fastURLPathEncode(builder.String()), nil
|
||||
}
|
||||
|
||||
func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) {
|
||||
if len(req.URL.RawPath) > 0 {
|
||||
return req.URL.RawPath, nil
|
||||
}
|
||||
return req.URL.EscapedPath(), nil
|
||||
return fastURLPathEncode(req.URL.Path), nil
|
||||
}
|
||||
|
||||
func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
|
||||
|
||||
30
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
generated
vendored
30
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
generated
vendored
@@ -29,6 +29,7 @@ type errNotAcceptable struct {
|
||||
accepted []string
|
||||
}
|
||||
|
||||
// NewNotAcceptableError returns an error of NotAcceptable which contains specified string
|
||||
func NewNotAcceptableError(accepted []string) error {
|
||||
return errNotAcceptable{accepted}
|
||||
}
|
||||
@@ -46,11 +47,40 @@ func (e errNotAcceptable) Status() metav1.Status {
|
||||
}
|
||||
}
|
||||
|
||||
// errNotAcceptableConversion indicates Accept negotiation has failed specifically
|
||||
// for a conversion to a known type.
|
||||
type errNotAcceptableConversion struct {
|
||||
target string
|
||||
accepted []string
|
||||
}
|
||||
|
||||
// NewNotAcceptableConversionError returns an error indicating that the desired
|
||||
// API transformation to the target group version kind string is not accepted and
|
||||
// only the listed mime types are allowed. This is temporary while Table does not
|
||||
// yet support protobuf encoding.
|
||||
func NewNotAcceptableConversionError(target string, accepted []string) error {
|
||||
return errNotAcceptableConversion{target, accepted}
|
||||
}
|
||||
|
||||
func (e errNotAcceptableConversion) Error() string {
|
||||
return fmt.Sprintf("only the following media types are accepted when converting to %s: %v", e.target, strings.Join(e.accepted, ", "))
|
||||
}
|
||||
|
||||
func (e errNotAcceptableConversion) Status() metav1.Status {
|
||||
return metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotAcceptable,
|
||||
Reason: metav1.StatusReasonNotAcceptable,
|
||||
Message: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// errUnsupportedMediaType indicates Content-Type is not recognized
|
||||
type errUnsupportedMediaType struct {
|
||||
accepted []string
|
||||
}
|
||||
|
||||
// NewUnsupportedMediaTypeError returns an error of UnsupportedMediaType which contains specified string
|
||||
func NewUnsupportedMediaTypeError(accepted []string) error {
|
||||
return errUnsupportedMediaType{accepted}
|
||||
}
|
||||
|
||||
103
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
generated
vendored
103
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
generated
vendored
@@ -22,7 +22,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/ww/goautoneg"
|
||||
"github.com/munnerz/goautoneg"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -43,33 +43,27 @@ func MediaTypesForSerializer(ns runtime.NegotiatedSerializer) (mediaTypes, strea
|
||||
// NegotiateOutputMediaType negotiates the output structured media type and a serializer, or
|
||||
// returns an error.
|
||||
func NegotiateOutputMediaType(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (MediaTypeOptions, runtime.SerializerInfo, error) {
|
||||
mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), AcceptedMediaTypesForEndpoint(ns), restrictions)
|
||||
mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), ns.SupportedMediaTypes(), restrictions)
|
||||
if !ok {
|
||||
supported, _ := MediaTypesForSerializer(ns)
|
||||
return mediaType, runtime.SerializerInfo{}, NewNotAcceptableError(supported)
|
||||
}
|
||||
// TODO: move into resthandler
|
||||
info := mediaType.Accepted.Serializer
|
||||
info := mediaType.Accepted
|
||||
if (mediaType.Pretty || isPrettyPrint(req)) && info.PrettySerializer != nil {
|
||||
info.Serializer = info.PrettySerializer
|
||||
}
|
||||
return mediaType, info, nil
|
||||
}
|
||||
|
||||
// NegotiateOutputSerializer returns a serializer for the output.
|
||||
func NegotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||
_, info, err := NegotiateOutputMediaType(req, ns, DefaultEndpointRestrictions)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// NegotiateOutputStreamSerializer returns a stream serializer for the given request.
|
||||
func NegotiateOutputStreamSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||
mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), AcceptedMediaTypesForEndpoint(ns), DefaultEndpointRestrictions)
|
||||
if !ok || mediaType.Accepted.Serializer.StreamSerializer == nil {
|
||||
// NegotiateOutputMediaTypeStream returns a stream serializer for the given request.
|
||||
func NegotiateOutputMediaTypeStream(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (runtime.SerializerInfo, error) {
|
||||
mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), ns.SupportedMediaTypes(), restrictions)
|
||||
if !ok || mediaType.Accepted.StreamSerializer == nil {
|
||||
_, supported := MediaTypesForSerializer(ns)
|
||||
return runtime.SerializerInfo{}, NewNotAcceptableError(supported)
|
||||
}
|
||||
return mediaType.Accepted.Serializer, nil
|
||||
return mediaType.Accepted, nil
|
||||
}
|
||||
|
||||
// NegotiateInputSerializer returns the input serializer for the provided request.
|
||||
@@ -85,10 +79,7 @@ func NegotiateInputSerializerForMediaType(mediaType string, streaming bool, ns r
|
||||
mediaType = mediaTypes[0].MediaType
|
||||
}
|
||||
if mediaType, _, err := mime.ParseMediaType(mediaType); err == nil {
|
||||
for _, info := range mediaTypes {
|
||||
if info.MediaType != mediaType {
|
||||
continue
|
||||
}
|
||||
if info, ok := runtime.SerializerInfoForMediaType(mediaTypes, mediaType); ok {
|
||||
return info, nil
|
||||
}
|
||||
}
|
||||
@@ -105,10 +96,13 @@ func NegotiateInputSerializerForMediaType(mediaType string, streaming bool, ns r
|
||||
func isPrettyPrint(req *http.Request) bool {
|
||||
// DEPRECATED: should be part of the content type
|
||||
if req.URL != nil {
|
||||
pp := req.URL.Query().Get("pretty")
|
||||
if len(pp) > 0 {
|
||||
pretty, _ := strconv.ParseBool(pp)
|
||||
return pretty
|
||||
// avoid an allocation caused by parsing the URL query
|
||||
if strings.Contains(req.URL.RawQuery, "pretty") {
|
||||
pp := req.URL.Query().Get("pretty")
|
||||
if len(pp) > 0 {
|
||||
pretty, _ := strconv.ParseBool(pp)
|
||||
return pretty
|
||||
}
|
||||
}
|
||||
}
|
||||
userAgent := req.UserAgent()
|
||||
@@ -122,9 +116,10 @@ func isPrettyPrint(req *http.Request) bool {
|
||||
// EndpointRestrictions is an interface that allows content-type negotiation
|
||||
// to verify server support for specific options
|
||||
type EndpointRestrictions interface {
|
||||
// AllowsConversion should return true if the specified group version kind
|
||||
// is an allowed target object.
|
||||
AllowsConversion(schema.GroupVersionKind) bool
|
||||
// AllowsMediaTypeTransform returns true if the endpoint allows either the requested mime type
|
||||
// or the requested transformation. If false, the caller should ignore this mime type. If the
|
||||
// target is nil, the client is not requesting a transformation.
|
||||
AllowsMediaTypeTransform(mimeType, mimeSubType string, target *schema.GroupVersionKind) bool
|
||||
// AllowsServerVersion should return true if the specified version is valid
|
||||
// for the server group.
|
||||
AllowsServerVersion(version string) bool
|
||||
@@ -133,24 +128,17 @@ type EndpointRestrictions interface {
|
||||
AllowsStreamSchema(schema string) bool
|
||||
}
|
||||
|
||||
// DefaultEndpointRestrictions is the default EndpointRestrictions which allows
|
||||
// content-type negotiation to verify server support for specific options
|
||||
var DefaultEndpointRestrictions = emptyEndpointRestrictions{}
|
||||
|
||||
type emptyEndpointRestrictions struct{}
|
||||
|
||||
func (emptyEndpointRestrictions) AllowsConversion(schema.GroupVersionKind) bool { return false }
|
||||
func (emptyEndpointRestrictions) AllowsServerVersion(string) bool { return false }
|
||||
func (emptyEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" }
|
||||
|
||||
// AcceptedMediaType contains information about a valid media type that the
|
||||
// server can serialize.
|
||||
type AcceptedMediaType struct {
|
||||
// Type is the first part of the media type ("application")
|
||||
Type string
|
||||
// SubType is the second part of the media type ("json")
|
||||
SubType string
|
||||
// Serializer is the serialization info this object accepts
|
||||
Serializer runtime.SerializerInfo
|
||||
func (emptyEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool {
|
||||
return gvk == nil
|
||||
}
|
||||
func (emptyEndpointRestrictions) AllowsServerVersion(string) bool { return false }
|
||||
func (emptyEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" }
|
||||
|
||||
// MediaTypeOptions describes information for a given media type that may alter
|
||||
// the server response
|
||||
@@ -178,13 +166,13 @@ type MediaTypeOptions struct {
|
||||
Unrecognized []string
|
||||
|
||||
// the accepted media type from the client
|
||||
Accepted *AcceptedMediaType
|
||||
Accepted runtime.SerializerInfo
|
||||
}
|
||||
|
||||
// acceptMediaTypeOptions returns an options object that matches the provided media type params. If
|
||||
// it returns false, the provided options are not allowed and the media type must be skipped. These
|
||||
// parameters are unversioned and may not be changed.
|
||||
func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType, endpoint EndpointRestrictions) (MediaTypeOptions, bool) {
|
||||
func acceptMediaTypeOptions(params map[string]string, accepts *runtime.SerializerInfo, endpoint EndpointRestrictions) (MediaTypeOptions, bool) {
|
||||
var options MediaTypeOptions
|
||||
|
||||
// extract all known parameters
|
||||
@@ -210,7 +198,7 @@ func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType
|
||||
|
||||
// controls the streaming schema
|
||||
case "stream":
|
||||
if len(v) > 0 && (accepts.Serializer.StreamSerializer == nil || !endpoint.AllowsStreamSchema(v)) {
|
||||
if len(v) > 0 && (accepts.StreamSerializer == nil || !endpoint.AllowsStreamSchema(v)) {
|
||||
return MediaTypeOptions{}, false
|
||||
}
|
||||
options.Stream = v
|
||||
@@ -238,16 +226,16 @@ func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType
|
||||
}
|
||||
}
|
||||
|
||||
if options.Convert != nil && !endpoint.AllowsConversion(*options.Convert) {
|
||||
if !endpoint.AllowsMediaTypeTransform(accepts.MediaTypeType, accepts.MediaTypeSubType, options.Convert) {
|
||||
return MediaTypeOptions{}, false
|
||||
}
|
||||
|
||||
options.Accepted = accepts
|
||||
options.Accepted = *accepts
|
||||
return options, true
|
||||
}
|
||||
|
||||
type candidateMediaType struct {
|
||||
accepted *AcceptedMediaType
|
||||
accepted *runtime.SerializerInfo
|
||||
clauses goautoneg.Accept
|
||||
}
|
||||
|
||||
@@ -255,10 +243,10 @@ type candidateMediaTypeSlice []candidateMediaType
|
||||
|
||||
// NegotiateMediaTypeOptions returns the most appropriate content type given the accept header and
|
||||
// a list of alternatives along with the accepted media type parameters.
|
||||
func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endpoint EndpointRestrictions) (MediaTypeOptions, bool) {
|
||||
func NegotiateMediaTypeOptions(header string, accepted []runtime.SerializerInfo, endpoint EndpointRestrictions) (MediaTypeOptions, bool) {
|
||||
if len(header) == 0 && len(accepted) > 0 {
|
||||
return MediaTypeOptions{
|
||||
Accepted: &accepted[0],
|
||||
Accepted: accepted[0],
|
||||
}, true
|
||||
}
|
||||
|
||||
@@ -268,8 +256,8 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp
|
||||
for i := range accepted {
|
||||
accepts := &accepted[i]
|
||||
switch {
|
||||
case clause.Type == accepts.Type && clause.SubType == accepts.SubType,
|
||||
clause.Type == accepts.Type && clause.SubType == "*",
|
||||
case clause.Type == accepts.MediaTypeType && clause.SubType == accepts.MediaTypeSubType,
|
||||
clause.Type == accepts.MediaTypeType && clause.SubType == "*",
|
||||
clause.Type == "*" && clause.SubType == "*":
|
||||
candidates = append(candidates, candidateMediaType{accepted: accepts, clauses: clause})
|
||||
}
|
||||
@@ -284,22 +272,3 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp
|
||||
|
||||
return MediaTypeOptions{}, false
|
||||
}
|
||||
|
||||
// AcceptedMediaTypesForEndpoint returns an array of structs that are used to efficiently check which
|
||||
// allowed media types the server exposes.
|
||||
func AcceptedMediaTypesForEndpoint(ns runtime.NegotiatedSerializer) []AcceptedMediaType {
|
||||
var acceptedMediaTypes []AcceptedMediaType
|
||||
for _, info := range ns.SupportedMediaTypes() {
|
||||
segments := strings.SplitN(info.MediaType, "/", 2)
|
||||
if len(segments) == 1 {
|
||||
segments = append(segments, "*")
|
||||
}
|
||||
t := AcceptedMediaType{
|
||||
Type: segments[0],
|
||||
SubType: segments[1],
|
||||
Serializer: info,
|
||||
}
|
||||
acceptedMediaTypes = append(acceptedMediaTypes, t)
|
||||
}
|
||||
return acceptedMediaTypes
|
||||
}
|
||||
|
||||
342
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
342
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
@@ -23,9 +23,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/evanphx/json-patch"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
@@ -36,15 +36,18 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -53,10 +56,10 @@ const (
|
||||
)
|
||||
|
||||
// PatchResource returns a function that will handle a resource patch.
|
||||
func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc {
|
||||
func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
// For performance tracking purposes.
|
||||
trace := utiltrace.New("Patch " + req.URL.Path)
|
||||
trace := utiltrace.New("Patch", utiltrace.Field{"url", req.URL.Path})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -90,36 +93,48 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
||||
return
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
defer cancel()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
patchJS, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
options := &metav1.UpdateOptions{}
|
||||
patchBytes, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
options := &metav1.PatchOptions{}
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
if errs := validation.ValidateUpdateOptions(options); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "UpdateOptions"}, "", errs)
|
||||
if errs := validation.ValidatePatchOptions(options, patchType); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "PatchOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PatchOptions"))
|
||||
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
|
||||
audit.LogRequestPatch(ae, patchJS)
|
||||
audit.LogRequestPatch(ae, patchBytes)
|
||||
trace.Step("Recorded the audit event")
|
||||
|
||||
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
baseContentType := runtime.ContentTypeJSON
|
||||
if patchType == types.ApplyPatchType {
|
||||
baseContentType = runtime.ContentTypeYAML
|
||||
}
|
||||
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("no serializer defined for JSON"), w, req)
|
||||
scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req)
|
||||
return
|
||||
}
|
||||
gv := scope.Kind.GroupVersion()
|
||||
@@ -130,7 +145,19 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
||||
)
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
staticAdmissionAttributes := admission.NewAttributesRecord(
|
||||
staticCreateAttributes := admission.NewAttributesRecord(
|
||||
nil,
|
||||
nil,
|
||||
scope.Kind,
|
||||
namespace,
|
||||
name,
|
||||
scope.Resource,
|
||||
scope.Subresource,
|
||||
admission.Create,
|
||||
patchToCreateOptions(options),
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
userInfo)
|
||||
staticUpdateAttributes := admission.NewAttributesRecord(
|
||||
nil,
|
||||
nil,
|
||||
scope.Kind,
|
||||
@@ -139,41 +166,43 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
||||
scope.Resource,
|
||||
scope.Subresource,
|
||||
admission.Update,
|
||||
patchToUpdateOptions(options),
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
userInfo,
|
||||
)
|
||||
admissionCheck := func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
// if we allow create-on-patch, we have this TODO: call the mutating admission chain with the CREATE verb instead of UPDATE
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && admit.Handles(admission.Update) {
|
||||
return mutatingAdmission.Admit(admission.NewAttributesRecord(
|
||||
updatedObject,
|
||||
currentObject,
|
||||
scope.Kind,
|
||||
namespace,
|
||||
name,
|
||||
scope.Resource,
|
||||
scope.Subresource,
|
||||
admission.Update,
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
userInfo,
|
||||
))
|
||||
}
|
||||
return nil
|
||||
|
||||
mutatingAdmission, _ := admit.(admission.MutationInterface)
|
||||
createAuthorizerAttributes := authorizer.AttributesRecord{
|
||||
User: userInfo,
|
||||
ResourceRequest: true,
|
||||
Path: req.URL.Path,
|
||||
Verb: "create",
|
||||
APIGroup: scope.Resource.Group,
|
||||
APIVersion: scope.Resource.Version,
|
||||
Resource: scope.Resource.Resource,
|
||||
Subresource: scope.Subresource,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
p := patcher{
|
||||
namer: scope.Namer,
|
||||
creater: scope.Creater,
|
||||
defaulter: scope.Defaulter,
|
||||
typer: scope.Typer,
|
||||
unsafeConvertor: scope.UnsafeConvertor,
|
||||
kind: scope.Kind,
|
||||
resource: scope.Resource,
|
||||
subresource: scope.Subresource,
|
||||
dryRun: dryrun.IsDryRun(options.DryRun),
|
||||
|
||||
objectInterfaces: scope,
|
||||
|
||||
hubGroupVersion: scope.HubGroupVersion,
|
||||
|
||||
createValidation: rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
|
||||
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
|
||||
admissionCheck: admissionCheck,
|
||||
createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, scope), scope.Authorizer, createAuthorizerAttributes),
|
||||
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, scope),
|
||||
admissionCheck: mutatingAdmission,
|
||||
|
||||
codec: codec,
|
||||
|
||||
@@ -183,35 +212,34 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
||||
restPatcher: r,
|
||||
name: name,
|
||||
patchType: patchType,
|
||||
patchJS: patchJS,
|
||||
patchBytes: patchBytes,
|
||||
userAgent: req.UserAgent(),
|
||||
|
||||
trace: trace,
|
||||
}
|
||||
|
||||
result, err := p.patchResource(ctx)
|
||||
result, wasCreated, err := p.patchResource(ctx, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
trace.Step("Object stored in database")
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
if err := setObjectSelfLink(ctx, result, req, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
trace.Step("Self-link added")
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
status := http.StatusOK
|
||||
if wasCreated {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
transformResponseObject(ctx, scope, trace, req, w, status, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
type mutateObjectUpdateFunc func(obj, old runtime.Object) error
|
||||
type mutateObjectUpdateFunc func(ctx context.Context, obj, old runtime.Object) error
|
||||
|
||||
// patcher breaks the process of patch application and retries into smaller
|
||||
// pieces of functionality.
|
||||
@@ -223,27 +251,33 @@ type patcher struct {
|
||||
namer ScopeNamer
|
||||
creater runtime.ObjectCreater
|
||||
defaulter runtime.ObjectDefaulter
|
||||
typer runtime.ObjectTyper
|
||||
unsafeConvertor runtime.ObjectConvertor
|
||||
resource schema.GroupVersionResource
|
||||
kind schema.GroupVersionKind
|
||||
subresource string
|
||||
dryRun bool
|
||||
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
|
||||
hubGroupVersion schema.GroupVersion
|
||||
|
||||
// Validation functions
|
||||
createValidation rest.ValidateObjectFunc
|
||||
updateValidation rest.ValidateObjectUpdateFunc
|
||||
admissionCheck mutateObjectUpdateFunc
|
||||
admissionCheck admission.MutationInterface
|
||||
|
||||
codec runtime.Codec
|
||||
|
||||
timeout time.Duration
|
||||
options *metav1.UpdateOptions
|
||||
options *metav1.PatchOptions
|
||||
|
||||
// Operation information
|
||||
restPatcher rest.Patcher
|
||||
name string
|
||||
patchType types.PatchType
|
||||
patchJS []byte
|
||||
patchBytes []byte
|
||||
userAgent string
|
||||
|
||||
trace *utiltrace.Trace
|
||||
|
||||
@@ -251,14 +285,18 @@ type patcher struct {
|
||||
namespace string
|
||||
updatedObjectInfo rest.UpdatedObjectInfo
|
||||
mechanism patchMechanism
|
||||
forceAllowCreate bool
|
||||
}
|
||||
|
||||
type patchMechanism interface {
|
||||
applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error)
|
||||
createNewObject() (runtime.Object, error)
|
||||
}
|
||||
|
||||
type jsonPatcher struct {
|
||||
*patcher
|
||||
|
||||
fieldManager *fieldmanager.FieldManager
|
||||
}
|
||||
|
||||
func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
||||
@@ -277,18 +315,38 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r
|
||||
// Construct the resulting typed, unversioned object.
|
||||
objToUpdate := p.restPatcher.New()
|
||||
if err := runtime.DecodeInto(p.codec, patchedObjJS, objToUpdate); err != nil {
|
||||
return nil, err
|
||||
return nil, errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), string(patchedObjJS), err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
if p.fieldManager != nil {
|
||||
if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
|
||||
return nil, fmt.Errorf("failed to update object (json PATCH for %v) managed fields: %v", p.kind, err)
|
||||
}
|
||||
}
|
||||
return objToUpdate, nil
|
||||
}
|
||||
|
||||
// patchJS applies the patch. Input and output objects must both have
|
||||
func (p *jsonPatcher) createNewObject() (runtime.Object, error) {
|
||||
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
|
||||
}
|
||||
|
||||
// applyJSPatch applies the patch. Input and output objects must both have
|
||||
// the external version, since that is what the patch must have been constructed against.
|
||||
func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
|
||||
switch p.patchType {
|
||||
case types.JSONPatchType:
|
||||
patchObj, err := jsonpatch.DecodePatch(p.patchJS)
|
||||
// sanity check potentially abusive patches
|
||||
// TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789)
|
||||
if len(p.patchBytes) > 1024*1024 {
|
||||
v := []interface{}{}
|
||||
if err := json.Unmarshal(p.patchBytes, &v); err != nil {
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
patchObj, err := jsonpatch.DecodePatch(p.patchBytes)
|
||||
if err != nil {
|
||||
return nil, errors.NewBadRequest(err.Error())
|
||||
}
|
||||
@@ -303,7 +361,16 @@ func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr
|
||||
}
|
||||
return patchedJS, nil
|
||||
case types.MergePatchType:
|
||||
return jsonpatch.MergePatch(versionedJS, p.patchJS)
|
||||
// sanity check potentially abusive patches
|
||||
// TODO(liggitt): drop this once golang json parser limits stack depth (https://github.com/golang/go/issues/31789)
|
||||
if len(p.patchBytes) > 1024*1024 {
|
||||
v := map[string]interface{}{}
|
||||
if err := json.Unmarshal(p.patchBytes, &v); err != nil {
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding patch: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
return jsonpatch.MergePatch(versionedJS, p.patchBytes)
|
||||
default:
|
||||
// only here as a safety net - go-restful filters content-type
|
||||
return nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType)
|
||||
@@ -315,6 +382,7 @@ type smpPatcher struct {
|
||||
|
||||
// Schema
|
||||
schemaReferenceObj runtime.Object
|
||||
fieldManager *fieldmanager.FieldManager
|
||||
}
|
||||
|
||||
func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
||||
@@ -328,22 +396,63 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchJS, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
|
||||
if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert the object back to the hub version
|
||||
return p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion)
|
||||
newObj, err := p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.fieldManager != nil {
|
||||
if newObj, err = p.fieldManager.Update(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
|
||||
return nil, fmt.Errorf("failed to update object (smp PATCH for %v) managed fields: %v", p.kind, err)
|
||||
}
|
||||
}
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// strategicPatchObject applies a strategic merge patch of <patchJS> to
|
||||
func (p *smpPatcher) createNewObject() (runtime.Object, error) {
|
||||
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
|
||||
}
|
||||
|
||||
type applyPatcher struct {
|
||||
patch []byte
|
||||
options *metav1.PatchOptions
|
||||
creater runtime.ObjectCreater
|
||||
kind schema.GroupVersionKind
|
||||
fieldManager *fieldmanager.FieldManager
|
||||
}
|
||||
|
||||
func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) {
|
||||
force := false
|
||||
if p.options.Force != nil {
|
||||
force = *p.options.Force
|
||||
}
|
||||
if p.fieldManager == nil {
|
||||
panic("FieldManager must be installed to run apply")
|
||||
}
|
||||
return p.fieldManager.Apply(obj, p.patch, p.options.FieldManager, force)
|
||||
}
|
||||
|
||||
func (p *applyPatcher) createNewObject() (runtime.Object, error) {
|
||||
obj, err := p.creater.New(p.kind)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new object: %v", err)
|
||||
}
|
||||
return p.applyPatchToCurrentObject(obj)
|
||||
}
|
||||
|
||||
// strategicPatchObject applies a strategic merge patch of <patchBytes> to
|
||||
// <originalObject> and stores the result in <objToUpdate>.
|
||||
// It additionally returns the map[string]interface{} representation of the
|
||||
// <originalObject> and <patchJS>.
|
||||
// <originalObject> and <patchBytes>.
|
||||
// NOTE: Both <originalObject> and <objToUpdate> are supposed to be versioned.
|
||||
func strategicPatchObject(
|
||||
defaulter runtime.ObjectDefaulter,
|
||||
originalObject runtime.Object,
|
||||
patchJS []byte,
|
||||
patchBytes []byte,
|
||||
objToUpdate runtime.Object,
|
||||
schemaReferenceObj runtime.Object,
|
||||
) error {
|
||||
@@ -353,7 +462,7 @@ func strategicPatchObject(
|
||||
}
|
||||
|
||||
patchMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal(patchJS, &patchMap); err != nil {
|
||||
if err := json.Unmarshal(patchBytes, &patchMap); err != nil {
|
||||
return errors.NewBadRequest(err.Error())
|
||||
}
|
||||
|
||||
@@ -365,52 +474,113 @@ func strategicPatchObject(
|
||||
|
||||
// applyPatch is called every time GuaranteedUpdate asks for the updated object,
|
||||
// and is given the currently persisted object as input.
|
||||
func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (runtime.Object, error) {
|
||||
// TODO: rename this function because the name implies it is related to applyPatcher
|
||||
func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
|
||||
// Make sure we actually have a persisted currentObject
|
||||
p.trace.Step("About to apply patch")
|
||||
if hasUID, err := hasUID(currentObject); err != nil {
|
||||
currentObjectHasUID, err := hasUID(currentObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasUID {
|
||||
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
|
||||
} else if !currentObjectHasUID {
|
||||
objToUpdate, patchErr = p.mechanism.createNewObject()
|
||||
} else {
|
||||
objToUpdate, patchErr = p.mechanism.applyPatchToCurrentObject(currentObject)
|
||||
}
|
||||
|
||||
objToUpdate, err := p.mechanism.applyPatchToCurrentObject(currentObject)
|
||||
if patchErr != nil {
|
||||
return nil, patchErr
|
||||
}
|
||||
|
||||
objToUpdateHasUID, err := hasUID(objToUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if objToUpdateHasUID && !currentObjectHasUID {
|
||||
accessor, err := meta.Accessor(objToUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.NewConflict(p.resource.GroupResource(), p.name, fmt.Errorf("uid mismatch: the provided object specified uid %s, and no existing object was found", accessor.GetUID()))
|
||||
}
|
||||
|
||||
if err := checkName(objToUpdate, p.name, p.namespace, p.namer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return objToUpdate, nil
|
||||
}
|
||||
|
||||
func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation, operationOptions runtime.Object) admission.Attributes {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, operationOptions, p.dryRun, userInfo)
|
||||
}
|
||||
|
||||
// applyAdmission is called every time GuaranteedUpdate asks for the updated object,
|
||||
// and is given the currently persisted object and the patched object as input.
|
||||
// TODO: rename this function because the name implies it is related to applyPatcher
|
||||
func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) {
|
||||
p.trace.Step("About to check admission control")
|
||||
return patchedObject, p.admissionCheck(patchedObject, currentObject)
|
||||
var operation admission.Operation
|
||||
var options runtime.Object
|
||||
if hasUID, err := hasUID(currentObject); err != nil {
|
||||
return nil, err
|
||||
} else if !hasUID {
|
||||
operation = admission.Create
|
||||
currentObject = nil
|
||||
options = patchToCreateOptions(p.options)
|
||||
} else {
|
||||
operation = admission.Update
|
||||
options = patchToUpdateOptions(p.options)
|
||||
}
|
||||
if p.admissionCheck != nil && p.admissionCheck.Handles(operation) {
|
||||
attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation, options)
|
||||
return patchedObject, p.admissionCheck.Admit(ctx, attributes, p.objectInterfaces)
|
||||
}
|
||||
return patchedObject, nil
|
||||
}
|
||||
|
||||
// patchResource divides PatchResource for easier unit testing
|
||||
func (p *patcher) patchResource(ctx context.Context) (runtime.Object, error) {
|
||||
func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runtime.Object, bool, error) {
|
||||
p.namespace = request.NamespaceValue(ctx)
|
||||
switch p.patchType {
|
||||
case types.JSONPatchType, types.MergePatchType:
|
||||
p.mechanism = &jsonPatcher{patcher: p}
|
||||
p.mechanism = &jsonPatcher{
|
||||
patcher: p,
|
||||
fieldManager: scope.FieldManager,
|
||||
}
|
||||
case types.StrategicMergePatchType:
|
||||
schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
p.mechanism = &smpPatcher{patcher: p, schemaReferenceObj: schemaReferenceObj}
|
||||
p.mechanism = &smpPatcher{
|
||||
patcher: p,
|
||||
schemaReferenceObj: schemaReferenceObj,
|
||||
fieldManager: scope.FieldManager,
|
||||
}
|
||||
// this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type
|
||||
case types.ApplyPatchType:
|
||||
p.mechanism = &applyPatcher{
|
||||
fieldManager: scope.FieldManager,
|
||||
patch: p.patchBytes,
|
||||
options: p.options,
|
||||
creater: p.creater,
|
||||
kind: p.kind,
|
||||
}
|
||||
p.forceAllowCreate = true
|
||||
default:
|
||||
return nil, fmt.Errorf("%v: unimplemented patch type", p.patchType)
|
||||
return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType)
|
||||
}
|
||||
|
||||
wasCreated := false
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
|
||||
return finishRequest(p.timeout, func() (runtime.Object, error) {
|
||||
updateObject, _, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, false, p.options)
|
||||
result, err := finishRequest(p.timeout, func() (runtime.Object, error) {
|
||||
// Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate
|
||||
options := patchToUpdateOptions(p.options)
|
||||
updateObject, created, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, p.forceAllowCreate, options)
|
||||
wasCreated = created
|
||||
return updateObject, updateErr
|
||||
})
|
||||
return result, wasCreated, err
|
||||
}
|
||||
|
||||
// applyPatchToObject applies a strategic merge patch of <patchMap> to
|
||||
@@ -430,7 +600,9 @@ func applyPatchToObject(
|
||||
|
||||
// Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil {
|
||||
return err
|
||||
return errors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), err.Error()),
|
||||
})
|
||||
}
|
||||
// Decoding from JSON to a versioned object would apply defaults, so we do the same here
|
||||
defaulter.Default(objToUpdate)
|
||||
@@ -449,3 +621,29 @@ func interpretStrategicMergePatchError(err error) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// patchToUpdateOptions creates an UpdateOptions with the same field values as the provided PatchOptions.
|
||||
func patchToUpdateOptions(po *metav1.PatchOptions) *metav1.UpdateOptions {
|
||||
if po == nil {
|
||||
return nil
|
||||
}
|
||||
uo := &metav1.UpdateOptions{
|
||||
DryRun: po.DryRun,
|
||||
FieldManager: po.FieldManager,
|
||||
}
|
||||
uo.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("UpdateOptions"))
|
||||
return uo
|
||||
}
|
||||
|
||||
// patchToCreateOptions creates an CreateOptions with the same field values as the provided PatchOptions.
|
||||
func patchToCreateOptions(po *metav1.PatchOptions) *metav1.CreateOptions {
|
||||
if po == nil {
|
||||
return nil
|
||||
}
|
||||
co := &metav1.CreateOptions{
|
||||
DryRun: po.DryRun,
|
||||
FieldManager: po.FieldManager,
|
||||
}
|
||||
co.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
|
||||
return co
|
||||
}
|
||||
|
||||
339
vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
generated
vendored
339
vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
generated
vendored
@@ -26,161 +26,97 @@ import (
|
||||
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/apis/meta/v1beta1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// transformObject takes the object as returned by storage and ensures it is in
|
||||
// the client's desired form, as well as ensuring any API level fields like self-link
|
||||
// are properly set.
|
||||
func transformObject(ctx context.Context, obj runtime.Object, opts interface{}, mediaType negotiation.MediaTypeOptions, scope *RequestScope, req *http.Request) (runtime.Object, error) {
|
||||
if _, ok := obj.(*metav1.Status); ok {
|
||||
return obj, nil
|
||||
}
|
||||
if err := setObjectSelfLink(ctx, obj, req, scope.Namer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch target := mediaType.Convert; {
|
||||
case target == nil:
|
||||
return obj, nil
|
||||
|
||||
case target.Kind == "PartialObjectMetadata":
|
||||
return asPartialObjectMetadata(obj, target.GroupVersion())
|
||||
|
||||
case target.Kind == "PartialObjectMetadataList":
|
||||
return asPartialObjectMetadataList(obj, target.GroupVersion())
|
||||
|
||||
case target.Kind == "Table":
|
||||
options, ok := opts.(*metav1beta1.TableOptions)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected TableOptions, got %T", opts)
|
||||
}
|
||||
return asTable(ctx, obj, options, scope, target.GroupVersion())
|
||||
|
||||
default:
|
||||
accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
|
||||
err := negotiation.NewNotAcceptableError(accepted)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// optionsForTransform will load and validate any additional query parameter options for
|
||||
// a conversion or return an error.
|
||||
func optionsForTransform(mediaType negotiation.MediaTypeOptions, req *http.Request) (interface{}, error) {
|
||||
switch target := mediaType.Convert; {
|
||||
case target == nil:
|
||||
case target.Kind == "Table" && (target.GroupVersion() == metav1beta1.SchemeGroupVersion || target.GroupVersion() == metav1.SchemeGroupVersion):
|
||||
opts := &metav1beta1.TableOptions{}
|
||||
if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch errs := validation.ValidateTableOptions(opts); len(errs) {
|
||||
case 0:
|
||||
return opts, nil
|
||||
case 1:
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("Unable to convert to Table as requested: %v", errs[0].Error()))
|
||||
default:
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("Unable to convert to Table as requested: %v", errs))
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// targetEncodingForTransform returns the appropriate serializer for the input media type
|
||||
func targetEncodingForTransform(scope *RequestScope, mediaType negotiation.MediaTypeOptions, req *http.Request) (schema.GroupVersionKind, runtime.NegotiatedSerializer, bool) {
|
||||
switch target := mediaType.Convert; {
|
||||
case target == nil:
|
||||
case (target.Kind == "PartialObjectMetadata" || target.Kind == "PartialObjectMetadataList" || target.Kind == "Table") &&
|
||||
(target.GroupVersion() == metav1beta1.SchemeGroupVersion || target.GroupVersion() == metav1.SchemeGroupVersion):
|
||||
return *target, metainternalversion.Codecs, true
|
||||
}
|
||||
return scope.Kind, scope.Serializer, false
|
||||
}
|
||||
|
||||
// transformResponseObject takes an object loaded from storage and performs any necessary transformations.
|
||||
// Will write the complete response object.
|
||||
func transformResponseObject(ctx context.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, result runtime.Object) {
|
||||
// TODO: fetch the media type much earlier in request processing and pass it into this method.
|
||||
trace := scope.Trace
|
||||
mediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
func transformResponseObject(ctx context.Context, scope *RequestScope, trace *utiltrace.Trace, req *http.Request, w http.ResponseWriter, statusCode int, mediaType negotiation.MediaTypeOptions, result runtime.Object) {
|
||||
options, err := optionsForTransform(mediaType, req)
|
||||
if err != nil {
|
||||
status := responsewriters.ErrorToAPIStatus(err)
|
||||
trace.Step("Writing raw JSON response")
|
||||
responsewriters.WriteRawJSON(int(status.Code), status, w)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// If conversion was allowed by the scope, perform it before writing the response
|
||||
if target := mediaType.Convert; target != nil {
|
||||
switch {
|
||||
|
||||
case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
if meta.IsListType(result) {
|
||||
// TODO: this should be calculated earlier
|
||||
err = newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result))
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
m, err := meta.Accessor(result)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
|
||||
trace.Step(fmt.Sprintf("Serializing response as type %s", info.MediaType))
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, partial)
|
||||
return
|
||||
|
||||
case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
if !meta.IsListType(result) {
|
||||
// TODO: this should be calculated earlier
|
||||
err = newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result))
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
list := &metav1beta1.PartialObjectMetadataList{}
|
||||
trace.Step("Processing list items")
|
||||
err := meta.EachListItem(result, func(obj runtime.Object) error {
|
||||
m, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
list.Items = append(list.Items, partial)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
|
||||
trace.Step(fmt.Sprintf("Serializing response as type %s", info.MediaType))
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, list)
|
||||
return
|
||||
|
||||
case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
// TODO: relax the version abstraction
|
||||
// TODO: skip if this is a status response (delete without body)?
|
||||
|
||||
opts := &metav1beta1.TableOptions{}
|
||||
trace.Step("Decoding parameters")
|
||||
if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace.Step("Converting to table")
|
||||
table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace.Step("Processing rows")
|
||||
for i := range table.Rows {
|
||||
item := &table.Rows[i]
|
||||
switch opts.IncludeObject {
|
||||
case metav1beta1.IncludeObject:
|
||||
item.Object.Object, err = scope.Convertor.ConvertToVersion(item.Object.Object, scope.Kind.GroupVersion())
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
// TODO: rely on defaulting for the value here?
|
||||
case metav1beta1.IncludeMetadata, "":
|
||||
m, err := meta.Accessor(item.Object.Object)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
// TODO: turn this into an internal type and do conversion in order to get object kind automatically set?
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
item.Object.Object = partial
|
||||
case metav1beta1.IncludeNone:
|
||||
item.Object.Object = nil
|
||||
default:
|
||||
// TODO: move this to validation on the table options?
|
||||
err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject))
|
||||
scope.err(err, w, req)
|
||||
}
|
||||
}
|
||||
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
|
||||
trace.Step(fmt.Sprintf("Serializing response as type %s", info.MediaType))
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, table)
|
||||
return
|
||||
|
||||
default:
|
||||
// this block should only be hit if scope AllowsConversion is incorrect
|
||||
accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
|
||||
err := negotiation.NewNotAcceptableError(accepted)
|
||||
status := responsewriters.ErrorToAPIStatus(err)
|
||||
trace.Step("Writing raw JSON response")
|
||||
responsewriters.WriteRawJSON(int(status.Code), status, w)
|
||||
return
|
||||
}
|
||||
obj, err := transformObject(ctx, result, options, mediaType, scope, req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace.Step("Writing response")
|
||||
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
kind, serializer, _ := targetEncodingForTransform(scope, mediaType, req)
|
||||
responsewriters.WriteObjectNegotiated(serializer, scope, kind.GroupVersion(), w, req, statusCode, obj)
|
||||
}
|
||||
|
||||
// errNotAcceptable indicates Accept negotiation has failed
|
||||
@@ -204,3 +140,118 @@ func (e errNotAcceptable) Status() metav1.Status {
|
||||
Message: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func asTable(ctx context.Context, result runtime.Object, opts *metav1beta1.TableOptions, scope *RequestScope, groupVersion schema.GroupVersion) (runtime.Object, error) {
|
||||
switch groupVersion {
|
||||
case metav1beta1.SchemeGroupVersion, metav1.SchemeGroupVersion:
|
||||
default:
|
||||
return nil, newNotAcceptableError(fmt.Sprintf("no Table exists in group version %s", groupVersion))
|
||||
}
|
||||
|
||||
obj, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
table := (*metav1.Table)(obj)
|
||||
|
||||
for i := range table.Rows {
|
||||
item := &table.Rows[i]
|
||||
switch opts.IncludeObject {
|
||||
case metav1.IncludeObject:
|
||||
item.Object.Object, err = scope.Convertor.ConvertToVersion(item.Object.Object, scope.Kind.GroupVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: rely on defaulting for the value here?
|
||||
case metav1.IncludeMetadata, "":
|
||||
m, err := meta.Accessor(item.Object.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: turn this into an internal type and do conversion in order to get object kind automatically set?
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata"))
|
||||
item.Object.Object = partial
|
||||
case metav1.IncludeNone:
|
||||
item.Object.Object = nil
|
||||
default:
|
||||
err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func asPartialObjectMetadata(result runtime.Object, groupVersion schema.GroupVersion) (runtime.Object, error) {
|
||||
if meta.IsListType(result) {
|
||||
err := newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result))
|
||||
return nil, err
|
||||
}
|
||||
switch groupVersion {
|
||||
case metav1beta1.SchemeGroupVersion, metav1.SchemeGroupVersion:
|
||||
default:
|
||||
return nil, newNotAcceptableError(fmt.Sprintf("no PartialObjectMetadataList exists in group version %s", groupVersion))
|
||||
}
|
||||
m, err := meta.Accessor(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata"))
|
||||
return partial, nil
|
||||
}
|
||||
|
||||
func asPartialObjectMetadataList(result runtime.Object, groupVersion schema.GroupVersion) (runtime.Object, error) {
|
||||
li, ok := result.(metav1.ListInterface)
|
||||
if !ok {
|
||||
return nil, newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result))
|
||||
}
|
||||
|
||||
gvk := groupVersion.WithKind("PartialObjectMetadata")
|
||||
switch {
|
||||
case groupVersion == metav1beta1.SchemeGroupVersion:
|
||||
list := &metav1beta1.PartialObjectMetadataList{}
|
||||
err := meta.EachListItem(result, func(obj runtime.Object) error {
|
||||
m, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
list.Items = append(list.Items, *partial)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list.SelfLink = li.GetSelfLink()
|
||||
list.ResourceVersion = li.GetResourceVersion()
|
||||
list.Continue = li.GetContinue()
|
||||
return list, nil
|
||||
|
||||
case groupVersion == metav1.SchemeGroupVersion:
|
||||
list := &metav1.PartialObjectMetadataList{}
|
||||
err := meta.EachListItem(result, func(obj runtime.Object) error {
|
||||
m, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
list.Items = append(list.Items, *partial)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list.SelfLink = li.GetSelfLink()
|
||||
list.ResourceVersion = li.GetResourceVersion()
|
||||
list.Continue = li.GetContinue()
|
||||
return list, nil
|
||||
|
||||
default:
|
||||
return nil, newNotAcceptableError(fmt.Sprintf("no PartialObjectMetadataList exists in group version %s", groupVersion))
|
||||
}
|
||||
}
|
||||
|
||||
15
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
generated
vendored
15
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
generated
vendored
@@ -38,11 +38,18 @@ func ErrorToAPIStatus(err error) *metav1.Status {
|
||||
if len(status.Status) == 0 {
|
||||
status.Status = metav1.StatusFailure
|
||||
}
|
||||
if status.Code == 0 {
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
if status.Code == 0 {
|
||||
status.Code = http.StatusOK
|
||||
case metav1.StatusFailure:
|
||||
}
|
||||
case metav1.StatusFailure:
|
||||
if status.Code == 0 {
|
||||
status.Code = http.StatusInternalServerError
|
||||
}
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("apiserver received an error with wrong status field : %#+v", err))
|
||||
if status.Code == 0 {
|
||||
status.Code = http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
222
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
222
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
@@ -17,11 +17,17 @@ limitations under the License.
|
||||
package responsewriters
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -31,47 +37,11 @@ import (
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/apiserver/pkg/util/flushwriter"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
)
|
||||
|
||||
// httpResponseWriterWithInit wraps http.ResponseWriter, and implements the io.Writer interface to be used
|
||||
// with encoding. The purpose is to allow for encoding to a stream, while accommodating a custom HTTP status code
|
||||
// if encoding fails, and meeting the encoder's io.Writer interface requirement.
|
||||
type httpResponseWriterWithInit struct {
|
||||
hasWritten bool
|
||||
mediaType string
|
||||
statusCode int
|
||||
innerW http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w httpResponseWriterWithInit) Write(b []byte) (n int, err error) {
|
||||
if !w.hasWritten {
|
||||
w.innerW.Header().Set("Content-Type", w.mediaType)
|
||||
w.innerW.WriteHeader(w.statusCode)
|
||||
w.hasWritten = true
|
||||
}
|
||||
|
||||
return w.innerW.Write(b)
|
||||
}
|
||||
|
||||
// WriteObject renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
||||
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
||||
// response. The Accept header and current API version will be passed in, and the output will be copied
|
||||
// directly to the response body. If content type is returned it is used, otherwise the content type will
|
||||
// be "application/octet-stream". All other objects are sent to standard JSON serialization.
|
||||
func WriteObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
||||
stream, ok := object.(rest.ResourceStreamer)
|
||||
if ok {
|
||||
requestInfo, _ := request.RequestInfoFrom(req.Context())
|
||||
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||
StreamObject(statusCode, gv, s, stream, w, req)
|
||||
})
|
||||
return
|
||||
}
|
||||
WriteObjectNegotiated(s, gv, w, req, statusCode, object)
|
||||
}
|
||||
|
||||
// StreamObject performs input stream negotiation from a ResourceStreamer and writes that to the response.
|
||||
// If the client requests a websocket upgrade, negotiate for a websocket reader protocol (because many
|
||||
// browser clients cannot easily handle binary streaming protocols).
|
||||
@@ -101,6 +71,10 @@ func StreamObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSe
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.WriteHeader(statusCode)
|
||||
// Flush headers, if possible
|
||||
if flusher, ok := w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
writer := w.(io.Writer)
|
||||
if flush {
|
||||
writer = flushwriter.Wrap(w)
|
||||
@@ -109,19 +83,154 @@ func StreamObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSe
|
||||
}
|
||||
|
||||
// SerializeObject renders an object in the content type negotiated by the client using the provided encoder.
|
||||
// The context is optional and can be nil.
|
||||
func SerializeObject(mediaType string, encoder runtime.Encoder, innerW http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
w := httpResponseWriterWithInit{mediaType: mediaType, innerW: innerW, statusCode: statusCode}
|
||||
|
||||
if err := encoder.Encode(object, w); err != nil {
|
||||
errSerializationFatal(err, encoder, w)
|
||||
// The context is optional and can be nil. This method will perform optional content compression if requested by
|
||||
// a client and the feature gate for APIResponseCompression is enabled.
|
||||
func SerializeObject(mediaType string, encoder runtime.Encoder, hw http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
w := &deferredResponseWriter{
|
||||
mediaType: mediaType,
|
||||
statusCode: statusCode,
|
||||
contentEncoding: negotiateContentEncoding(req),
|
||||
hw: hw,
|
||||
}
|
||||
|
||||
err := encoder.Encode(object, w)
|
||||
if err == nil {
|
||||
err = w.Close()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// make a best effort to write the object if a failure is detected
|
||||
utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
|
||||
status := ErrorToAPIStatus(err)
|
||||
candidateStatusCode := int(status.Code)
|
||||
// if the current status code is successful, allow the error's status code to overwrite it
|
||||
if statusCode >= http.StatusOK && statusCode < http.StatusBadRequest {
|
||||
w.statusCode = candidateStatusCode
|
||||
}
|
||||
output, err := runtime.Encode(encoder, status)
|
||||
if err != nil {
|
||||
w.mediaType = "text/plain"
|
||||
output = []byte(fmt.Sprintf("%s: %s", status.Reason, status.Message))
|
||||
}
|
||||
if _, err := w.Write(output); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a fallback JSON response: %v", err))
|
||||
}
|
||||
w.Close()
|
||||
}
|
||||
|
||||
var gzipPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
gw, err := gzip.NewWriterLevel(nil, defaultGzipContentEncodingLevel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return gw
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
// defaultGzipContentEncodingLevel is set to 4 which uses less CPU than the default level
|
||||
defaultGzipContentEncodingLevel = 4
|
||||
// defaultGzipThresholdBytes is compared to the size of the first write from the stream
|
||||
// (usually the entire object), and if the size is smaller no gzipping will be performed
|
||||
// if the client requests it.
|
||||
defaultGzipThresholdBytes = 128 * 1024
|
||||
)
|
||||
|
||||
// negotiateContentEncoding returns a supported client-requested content encoding for the
|
||||
// provided request. It will return the empty string if no supported content encoding was
|
||||
// found or if response compression is disabled.
|
||||
func negotiateContentEncoding(req *http.Request) string {
|
||||
encoding := req.Header.Get("Accept-Encoding")
|
||||
if len(encoding) == 0 {
|
||||
return ""
|
||||
}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.APIResponseCompression) {
|
||||
return ""
|
||||
}
|
||||
for len(encoding) > 0 {
|
||||
var token string
|
||||
if next := strings.Index(encoding, ","); next != -1 {
|
||||
token = encoding[:next]
|
||||
encoding = encoding[next+1:]
|
||||
} else {
|
||||
token = encoding
|
||||
encoding = ""
|
||||
}
|
||||
switch strings.TrimSpace(token) {
|
||||
case "gzip":
|
||||
return "gzip"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type deferredResponseWriter struct {
|
||||
mediaType string
|
||||
statusCode int
|
||||
contentEncoding string
|
||||
|
||||
hasWritten bool
|
||||
hw http.ResponseWriter
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (w *deferredResponseWriter) Write(p []byte) (n int, err error) {
|
||||
if w.hasWritten {
|
||||
return w.w.Write(p)
|
||||
}
|
||||
w.hasWritten = true
|
||||
|
||||
hw := w.hw
|
||||
header := hw.Header()
|
||||
switch {
|
||||
case w.contentEncoding == "gzip" && len(p) > defaultGzipThresholdBytes:
|
||||
header.Set("Content-Encoding", "gzip")
|
||||
header.Add("Vary", "Accept-Encoding")
|
||||
|
||||
gw := gzipPool.Get().(*gzip.Writer)
|
||||
gw.Reset(hw)
|
||||
|
||||
w.w = gw
|
||||
default:
|
||||
w.w = hw
|
||||
}
|
||||
|
||||
header.Set("Content-Type", w.mediaType)
|
||||
hw.WriteHeader(w.statusCode)
|
||||
return w.w.Write(p)
|
||||
}
|
||||
|
||||
func (w *deferredResponseWriter) Close() error {
|
||||
if !w.hasWritten {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
switch t := w.w.(type) {
|
||||
case *gzip.Writer:
|
||||
err = t.Close()
|
||||
t.Reset(nil)
|
||||
gzipPool.Put(t)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var nopCloser = ioutil.NopCloser(nil)
|
||||
|
||||
// WriteObjectNegotiated renders an object in the content type negotiated by the client.
|
||||
// The context is optional and can be nil.
|
||||
func WriteObjectNegotiated(s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
serializer, err := negotiation.NegotiateOutputSerializer(req, s)
|
||||
func WriteObjectNegotiated(s runtime.NegotiatedSerializer, restrictions negotiation.EndpointRestrictions, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
stream, ok := object.(rest.ResourceStreamer)
|
||||
if ok {
|
||||
requestInfo, _ := request.RequestInfoFrom(req.Context())
|
||||
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
|
||||
StreamObject(statusCode, gv, s, stream, w, req)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, serializer, err := negotiation.NegotiateOutputMediaType(req, s, restrictions)
|
||||
if err != nil {
|
||||
// if original statusCode was not successful we need to return the original error
|
||||
// we cannot hide it behind negotiation problems
|
||||
@@ -158,29 +267,10 @@ func ErrorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupV
|
||||
return code
|
||||
}
|
||||
|
||||
WriteObjectNegotiated(s, gv, w, req, code, status)
|
||||
WriteObjectNegotiated(s, negotiation.DefaultEndpointRestrictions, gv, w, req, code, status)
|
||||
return code
|
||||
}
|
||||
|
||||
// errSerializationFatal renders an error to the response, and if codec fails will render plaintext.
|
||||
// Returns the HTTP status code of the error.
|
||||
func errSerializationFatal(err error, codec runtime.Encoder, w httpResponseWriterWithInit) {
|
||||
utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
|
||||
status := ErrorToAPIStatus(err)
|
||||
candidateStatusCode := int(status.Code)
|
||||
// If original statusCode was not successful, we need to return the original error.
|
||||
// We cannot hide it behind serialization problems
|
||||
if w.statusCode >= http.StatusOK && w.statusCode < http.StatusBadRequest {
|
||||
w.statusCode = candidateStatusCode
|
||||
}
|
||||
output, err := runtime.Encode(codec, status)
|
||||
if err != nil {
|
||||
w.mediaType = "text/plain"
|
||||
output = []byte(fmt.Sprintf("%s: %s", status.Reason, status.Message))
|
||||
}
|
||||
w.Write(output)
|
||||
}
|
||||
|
||||
// WriteRawJSON writes a non-API object in JSON.
|
||||
func WriteRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
|
||||
output, err := json.MarshalIndent(object, "", " ")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user