mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-06-10 10:15:57 +00:00
vendored changes
This commit is contained in:
parent
d091fff18b
commit
128f9a29f5
522 changed files with 29974 additions and 25705 deletions
71
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
71
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
|
|
@ -34,6 +34,7 @@ type attributesRecord struct {
|
|||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
operation Operation
|
||||
options runtime.Object
|
||||
dryRun bool
|
||||
object runtime.Object
|
||||
oldObject runtime.Object
|
||||
|
|
@ -43,20 +44,24 @@ type attributesRecord struct {
|
|||
// But ValidatingAdmissionWebhook add annotations concurrently.
|
||||
annotations map[string]string
|
||||
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 {
|
||||
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 +89,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
|
||||
}
|
||||
|
|
@ -134,6 +143,46 @@ func (record *attributesRecord) AddAnnotation(key, value string) error {
|
|||
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 {
|
||||
|
|
|
|||
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.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.Webhooks[i]))
|
||||
}
|
||||
}
|
||||
return &ret
|
||||
return accessors
|
||||
}
|
||||
|
||||
type ValidatingWebhookConfigurationSorter []*v1beta1.ValidatingWebhookConfiguration
|
||||
|
|
|
|||
23
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
23
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
|
|
@ -41,6 +41,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
|
||||
|
|
@ -60,6 +62,9 @@ type Attributes interface {
|
|||
// 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.
|
||||
AddAnnotation(key, value string) 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
|
||||
|
|
@ -74,6 +79,8 @@ type ObjectInterfaces interface {
|
|||
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.
|
||||
|
|
@ -87,6 +94,22 @@ type AnnotationsGetter interface {
|
|||
GetAnnotations() 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.
|
||||
type Interface interface {
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
|
|
|
|||
48
vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
48
vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
|
|
@ -32,8 +32,8 @@ const (
|
|||
)
|
||||
|
||||
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.
|
||||
|
|
@ -153,14 +153,12 @@ func (m *AdmissionMetrics) ObserveWebhook(elapsed time.Duration, rejected bool,
|
|||
}
|
||||
|
||||
type metricSet struct {
|
||||
latencies *prometheus.HistogramVec
|
||||
deprecatedLatencies *prometheus.HistogramVec
|
||||
latenciesSummary *prometheus.SummaryVec
|
||||
deprecatedLatenciesSummary *prometheus.SummaryVec
|
||||
latencies *prometheus.HistogramVec
|
||||
latenciesSummary *prometheus.SummaryVec
|
||||
}
|
||||
|
||||
func newMetricSet(name string, labels []string, helpTemplate string, hasSummary bool) *metricSet {
|
||||
var summary, deprecatedSummary *prometheus.SummaryVec
|
||||
var summary *prometheus.SummaryVec
|
||||
if hasSummary {
|
||||
summary = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
|
|
@ -172,16 +170,6 @@ func newMetricSet(name string, labels []string, helpTemplate string, hasSummary
|
|||
},
|
||||
labels,
|
||||
)
|
||||
deprecatedSummary = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: fmt.Sprintf("%s_admission_latencies_milliseconds_summary", name),
|
||||
Help: fmt.Sprintf("(Deprecated) "+helpTemplate, "latency summary in milliseconds"),
|
||||
MaxAge: latencySummaryMaxAge,
|
||||
},
|
||||
labels,
|
||||
)
|
||||
}
|
||||
|
||||
return &metricSet{
|
||||
|
|
@ -195,56 +183,32 @@ func newMetricSet(name string, labels []string, helpTemplate string, hasSummary
|
|||
},
|
||||
labels,
|
||||
),
|
||||
deprecatedLatencies: prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: fmt.Sprintf("%s_admission_latencies_milliseconds", name),
|
||||
Help: fmt.Sprintf("(Deprecated) "+helpTemplate, "latency histogram in milliseconds"),
|
||||
Buckets: latencyBuckets,
|
||||
},
|
||||
labels,
|
||||
),
|
||||
|
||||
latenciesSummary: summary,
|
||||
deprecatedLatenciesSummary: deprecatedSummary,
|
||||
latenciesSummary: summary,
|
||||
}
|
||||
}
|
||||
|
||||
// MustRegister registers all the prometheus metrics in the metricSet.
|
||||
func (m *metricSet) mustRegister() {
|
||||
prometheus.MustRegister(m.latencies)
|
||||
prometheus.MustRegister(m.deprecatedLatencies)
|
||||
if m.latenciesSummary != nil {
|
||||
prometheus.MustRegister(m.latenciesSummary)
|
||||
}
|
||||
if m.deprecatedLatenciesSummary != nil {
|
||||
prometheus.MustRegister(m.deprecatedLatenciesSummary)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets all the prometheus metrics in the metricSet.
|
||||
func (m *metricSet) reset() {
|
||||
m.latencies.Reset()
|
||||
m.deprecatedLatencies.Reset()
|
||||
if m.latenciesSummary != nil {
|
||||
m.latenciesSummary.Reset()
|
||||
}
|
||||
if m.deprecatedLatenciesSummary != nil {
|
||||
m.deprecatedLatenciesSummary.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Observe records an observed admission event to all metrics in the metricSet.
|
||||
func (m *metricSet) observe(elapsed time.Duration, labels ...string) {
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
elapsedMicroseconds := float64(elapsed / time.Microsecond)
|
||||
m.latencies.WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
m.deprecatedLatencies.WithLabelValues(labels...).Observe(elapsedMicroseconds)
|
||||
if m.latenciesSummary != nil {
|
||||
m.latenciesSummary.WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
}
|
||||
if m.deprecatedLatenciesSummary != nil {
|
||||
m.deprecatedLatenciesSummary.WithLabelValues(labels...).Observe(elapsedMicroseconds)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
160
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
160
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
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 (
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// 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 string, h *v1beta1.MutatingWebhook) WebhookAccessor {
|
||||
return mutatingWebhookAccessor{uid: uid, MutatingWebhook: h}
|
||||
}
|
||||
|
||||
type mutatingWebhookAccessor struct {
|
||||
*v1beta1.MutatingWebhook
|
||||
uid string
|
||||
}
|
||||
|
||||
func (m mutatingWebhookAccessor) GetUID() string {
|
||||
return m.uid
|
||||
}
|
||||
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 string, h *v1beta1.ValidatingWebhook) WebhookAccessor {
|
||||
return validatingWebhookAccessor{uid: uid, ValidatingWebhook: h}
|
||||
}
|
||||
|
||||
type validatingWebhookAccessor struct {
|
||||
*v1beta1.ValidatingWebhook
|
||||
uid string
|
||||
}
|
||||
|
||||
func (v validatingWebhookAccessor) GetUID() string {
|
||||
return v.uid
|
||||
}
|
||||
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
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
69
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/conversion.go
generated
vendored
69
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/conversion.go
generated
vendored
|
|
@ -41,3 +41,72 @@ func ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind, o admission.O
|
|||
out.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// 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, o admission.ObjectInterfaces, 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
|
||||
}
|
||||
|
|
|
|||
128
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
128
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
|
|
@ -24,12 +24,15 @@ import (
|
|||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"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"
|
||||
)
|
||||
|
|
@ -41,8 +44,9 @@ type Webhook struct {
|
|||
sourceFactory sourceFactory
|
||||
|
||||
hookSource Source
|
||||
clientManager *webhook.ClientManager
|
||||
clientManager *webhookutil.ClientManager
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
dispatcher Dispatcher
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +56,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) {
|
||||
|
|
@ -61,23 +65,24 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
|
|||
return nil, err
|
||||
}
|
||||
|
||||
cm, err := webhook.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme)
|
||||
cm, err := webhookutil.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.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,
|
||||
namespaceMatcher: &namespace.Matcher{},
|
||||
objectMatcher: &object.Matcher{},
|
||||
dispatcher: dispatcherFactory(&cm),
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -85,13 +90,13 @@ 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)
|
||||
}
|
||||
|
||||
|
|
@ -125,23 +130,78 @@ func (a *Webhook) ValidateInitialization() error {
|
|||
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, o admission.ObjectInterfaces) error {
|
||||
if rules.IsWebhookConfigurationResource(attr) {
|
||||
|
|
@ -154,39 +214,5 @@ func (a *Webhook) Dispatch(attr admission.Attributes, o admission.ObjectInterfac
|
|||
// 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 := ConvertToGVK(oldObj, attr.GetKind(), o)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttr.VersionedOldObject = out
|
||||
}
|
||||
if obj := attr.GetObject(); obj != nil {
|
||||
out, err := ConvertToGVK(obj, attr.GetKind(), o)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttr.VersionedObject = out
|
||||
}
|
||||
return a.dispatcher.Dispatch(ctx, &versionedAttr, o, relevantHooks)
|
||||
return a.dispatcher.Dispatch(ctx, attr, o, hooks)
|
||||
}
|
||||
|
|
|
|||
148
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
148
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
|
|
@ -24,6 +24,7 @@ import (
|
|||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/klog"
|
||||
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
|
|
@ -35,76 +36,144 @@ import (
|
|||
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"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
)
|
||||
|
||||
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, o admission.ObjectInterfaces, 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 _, 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, o)
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr.Attributes, "admit", hook.Name)
|
||||
|
||||
changed, err := a.callAttrMutatingHook(ctx, hook, invocation, versionedAttr, o)
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, 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
|
||||
}
|
||||
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
return apierrors.NewInternalError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// convert attr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||
if attr.VersionedObject != nil {
|
||||
return o.GetObjectConvertor().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, o admission.ObjectInterfaces) error {
|
||||
if attr.IsDryRun() {
|
||||
|
||||
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.MutatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces) (bool, error) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Currently dispatcher only supports `v1beta1` AdmissionReview
|
||||
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
|
||||
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")}
|
||||
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")}
|
||||
}
|
||||
|
||||
// Make the webhook request
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
request := request.CreateAdmissionReview(attr, invocation)
|
||||
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook))
|
||||
if err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
r := client.Post().Context(ctx).Body(&request)
|
||||
|
|
@ -112,49 +181,49 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
|
|||
r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second)
|
||||
}
|
||||
if err := r.Do().Into(response); err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
|
||||
if response.Response == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
}
|
||||
|
||||
for k, v := range response.Response.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)
|
||||
return false, webhookerrors.ToStatusErr(h.Name, response.Response.Result)
|
||||
}
|
||||
|
||||
patchJS := response.Response.Patch
|
||||
if len(patchJS) == 0 {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
patchObj, err := jsonpatch.DecodePatch(patchJS)
|
||||
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))
|
||||
}
|
||||
|
||||
jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), false)
|
||||
objJS, err := runtime.Encode(jsonSerializer, attr.VersionedObject)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
patchedJS, err := patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
var newVersionedObject runtime.Object
|
||||
|
|
@ -163,17 +232,22 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
|
|||
// They are represented as Unstructured.
|
||||
newVersionedObject = &unstructured.Unstructured{}
|
||||
} else {
|
||||
newVersionedObject, err = o.GetObjectCreater().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 := 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)
|
||||
|
||||
attr.Dirty = true
|
||||
attr.VersionedObject = newVersionedObject
|
||||
o.GetObjectDefaulter().Default(attr.VersionedObject)
|
||||
return nil
|
||||
return changed, nil
|
||||
}
|
||||
|
|
|
|||
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()
|
||||
}
|
||||
}
|
||||
18
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go
generated
vendored
18
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,15 @@ func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attr
|
|||
// Also update the comment in types.go
|
||||
return true, nil
|
||||
}
|
||||
// TODO: adding an LRU cache to cache the translation
|
||||
selector, err := metav1.LabelSelectorAsSelector(h.GetNamespaceSelector())
|
||||
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 +117,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
|
||||
}
|
||||
|
|
|
|||
20
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
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 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"
|
||||
59
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go
generated
vendored
Normal file
59
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"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) {
|
||||
// TODO: adding an LRU cache to cache the translation
|
||||
selector, err := metav1.LabelSelectorAsSelector(h.GetObjectSelector())
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
if selector.Empty() {
|
||||
return true, nil
|
||||
}
|
||||
return matchObject(attr.GetObject(), selector) || matchObject(attr.GetOldObject(), selector), nil
|
||||
}
|
||||
39
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
39
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
|
|
@ -26,9 +26,14 @@ import (
|
|||
)
|
||||
|
||||
// CreateAdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.AdmissionReview {
|
||||
gvk := attr.GetKind()
|
||||
gvr := attr.GetResource()
|
||||
func CreateAdmissionReview(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),
|
||||
|
|
@ -56,18 +61,32 @@ 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: admissionv1beta1.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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go
generated
vendored
40
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go
generated
vendored
|
|
@ -17,33 +17,39 @@ limitations under the License.
|
|||
package util
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookutil "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
|
||||
// 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 webhook.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.ClientConfig.Service != nil {
|
||||
ret.Service = &webhook.ClientConfigService{
|
||||
Name: w.ClientConfig.Service.Name,
|
||||
Namespace: w.ClientConfig.Service.Namespace,
|
||||
if w.GetClientConfig().Service != nil {
|
||||
ret.Service = &webhookutil.ClientConfigService{
|
||||
Name: w.GetClientConfig().Service.Name,
|
||||
Namespace: w.GetClientConfig().Service.Namespace,
|
||||
}
|
||||
if w.ClientConfig.Service.Path != nil {
|
||||
ret.Service.Path = *w.ClientConfig.Service.Path
|
||||
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
|
||||
}
|
||||
|
||||
// HasAdmissionReviewVersion check whether a version is accepted by a given webhook.
|
||||
func HasAdmissionReviewVersion(a string, w *v1beta1.Webhook) bool {
|
||||
for _, b := range w.AdmissionReviewVersions {
|
||||
func HasAdmissionReviewVersion(a string, w webhook.WebhookAccessor) bool {
|
||||
for _, b := range w.GetAdmissionReviewVersions() {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
83
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
83
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
|
|
@ -22,48 +22,85 @@ 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"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
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, o admission.ObjectInterfaces, 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
|
||||
}
|
||||
|
||||
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)
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, 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)
|
||||
|
|
@ -98,10 +135,10 @@ 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)
|
||||
|
|
@ -110,15 +147,15 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook,
|
|||
|
||||
// Currently dispatcher only supports `v1beta1` AdmissionReview
|
||||
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
|
||||
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")}
|
||||
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")}
|
||||
}
|
||||
|
||||
// Make the webhook request
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
request := request.CreateAdmissionReview(attr, invocation)
|
||||
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook))
|
||||
if err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
r := client.Post().Context(ctx).Body(&request)
|
||||
|
|
@ -126,15 +163,15 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook,
|
|||
r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second)
|
||||
}
|
||||
if err := r.Do().Into(response); err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
|
||||
if response.Response == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
}
|
||||
for k, v := range response.Response.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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
6
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
|
|
@ -51,11 +51,13 @@ 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.
|
||||
|
|
|
|||
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.
|
||||
|
|
|
|||
62
vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
generated
vendored
Normal file
62
vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
generated
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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
|
||||
|
||||
// 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(a Attributes, o ObjectInterfaces) error {
|
||||
if mutator, ok := r.admissionChain.(MutationInterface); ok {
|
||||
err := mutator.Admit(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(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(a Attributes, o ObjectInterfaces) error {
|
||||
if validator, ok := r.admissionChain.(ValidationInterface); ok {
|
||||
return validator.Validate(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)
|
||||
}
|
||||
31
vendor/k8s.io/apiserver/pkg/admission/util.go
generated
vendored
31
vendor/k8s.io/apiserver/pkg/admission/util.go
generated
vendored
|
|
@ -18,11 +18,30 @@ package admission
|
|||
|
||||
import "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
type SchemeBasedObjectInterfaces struct {
|
||||
Scheme *runtime.Scheme
|
||||
type RuntimeObjectInterfaces struct {
|
||||
runtime.ObjectCreater
|
||||
runtime.ObjectTyper
|
||||
runtime.ObjectDefaulter
|
||||
runtime.ObjectConvertor
|
||||
runtime.EquivalentResourceMapper
|
||||
}
|
||||
|
||||
func (r *SchemeBasedObjectInterfaces) GetObjectCreater() runtime.ObjectCreater { return r.Scheme }
|
||||
func (r *SchemeBasedObjectInterfaces) GetObjectTyper() runtime.ObjectTyper { return r.Scheme }
|
||||
func (r *SchemeBasedObjectInterfaces) GetObjectDefaulter() runtime.ObjectDefaulter { return r.Scheme }
|
||||
func (r *SchemeBasedObjectInterfaces) GetObjectConvertor() runtime.ObjectConvertor { return r.Scheme }
|
||||
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
|
||||
}
|
||||
|
|
|
|||
29
vendor/k8s.io/apiserver/pkg/apis/audit/v1/generated.pb.go
generated
vendored
29
vendor/k8s.io/apiserver/pkg/apis/audit/v1/generated.pb.go
generated
vendored
|
|
@ -34,22 +34,29 @@ limitations under the License.
|
|||
*/
|
||||
package v1
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import (
|
||||
fmt "fmt"
|
||||
|
||||
import k8s_io_api_authentication_v1 "k8s.io/api/authentication/v1"
|
||||
import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import k8s_io_apimachinery_pkg_runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
import k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types"
|
||||
math "math"
|
||||
|
||||
import github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
|
||||
k8s_io_api_authentication_v1 "k8s.io/api/authentication/v1"
|
||||
|
||||
import strings "strings"
|
||||
import reflect "reflect"
|
||||
k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
import io "io"
|
||||
k8s_io_apimachinery_pkg_runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
|
||||
|
||||
strings "strings"
|
||||
|
||||
reflect "reflect"
|
||||
|
||||
io "io"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
|
|
|
|||
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))
|
||||
|
|
|
|||
29
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go
generated
vendored
29
vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/generated.pb.go
generated
vendored
|
|
@ -34,22 +34,29 @@ limitations under the License.
|
|||
*/
|
||||
package v1alpha1
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import (
|
||||
fmt "fmt"
|
||||
|
||||
import k8s_io_api_authentication_v1 "k8s.io/api/authentication/v1"
|
||||
import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import k8s_io_apimachinery_pkg_runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
import k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types"
|
||||
math "math"
|
||||
|
||||
import github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
|
||||
k8s_io_api_authentication_v1 "k8s.io/api/authentication/v1"
|
||||
|
||||
import strings "strings"
|
||||
import reflect "reflect"
|
||||
k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
import io "io"
|
||||
k8s_io_apimachinery_pkg_runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
|
||||
|
||||
strings "strings"
|
||||
|
||||
reflect "reflect"
|
||||
|
||||
io "io"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
|
|
|
|||
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))
|
||||
|
|
|
|||
29
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go
generated
vendored
29
vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1/generated.pb.go
generated
vendored
|
|
@ -34,22 +34,29 @@ limitations under the License.
|
|||
*/
|
||||
package v1beta1
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import (
|
||||
fmt "fmt"
|
||||
|
||||
import k8s_io_api_authentication_v1 "k8s.io/api/authentication/v1"
|
||||
import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import k8s_io_apimachinery_pkg_runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
import k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types"
|
||||
math "math"
|
||||
|
||||
import github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
|
||||
k8s_io_api_authentication_v1 "k8s.io/api/authentication/v1"
|
||||
|
||||
import strings "strings"
|
||||
import reflect "reflect"
|
||||
k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
import io "io"
|
||||
k8s_io_apimachinery_pkg_runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
|
||||
|
||||
strings "strings"
|
||||
|
||||
reflect "reflect"
|
||||
|
||||
io "io"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
|
|
|
|||
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))
|
||||
|
|
|
|||
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))
|
||||
|
|
|
|||
30
vendor/k8s.io/apiserver/pkg/audit/request.go
generated
vendored
30
vendor/k8s.io/apiserver/pkg/audit/request.go
generated
vendored
|
|
@ -197,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.
|
||||
|
|
|
|||
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
|
||||
}
|
||||
|
|
|
|||
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/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) {
|
||||
|
|
|
|||
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/groupversion.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
18
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
18
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
|
|
@ -43,7 +43,7 @@ import (
|
|||
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)
|
||||
|
|
@ -73,7 +73,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
|
||||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -106,6 +106,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()
|
||||
|
|
@ -128,9 +129,9 @@ 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)
|
||||
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, &scope)
|
||||
err = mutatingAdmission.Admit(admissionAttributes, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -157,7 +158,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
ctx,
|
||||
name,
|
||||
obj,
|
||||
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, &scope),
|
||||
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
|
||||
options,
|
||||
)
|
||||
})
|
||||
|
|
@ -173,18 +174,17 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
status.Code = int32(code)
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, code, outputMediaType, 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)
|
||||
}
|
||||
|
||||
|
|
|
|||
64
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
64
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
|
|
@ -40,7 +40,7 @@ import (
|
|||
|
||||
// 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)
|
||||
|
|
@ -64,7 +64,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -113,29 +113,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, &scope); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
if err := validatingAdmission.Validate(attrs, &scope); 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
|
||||
})
|
||||
|
|
@ -168,13 +153,12 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||
}
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, status, outputMediaType, 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)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
|
@ -197,7 +181,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
ctx = request.WithNamespace(ctx, namespace)
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -237,6 +221,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 {
|
||||
|
|
@ -263,30 +249,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, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
err = validatingAdmission.Validate(attrs, &scope)
|
||||
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)
|
||||
|
|
@ -305,7 +274,6 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
}
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
11
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
|
|
@ -27,6 +27,7 @@ import (
|
|||
"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"
|
||||
|
|
@ -115,11 +116,15 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r
|
|||
internal.RemoveObjectManagedFields(newObjVersioned)
|
||||
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed new object: %v", err)
|
||||
// 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 nil, fmt.Errorf("failed to create typed live object: %v", err)
|
||||
// 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())
|
||||
manager, err = f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate)
|
||||
|
|
@ -156,7 +161,7 @@ func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager
|
|||
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
|
||||
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
|
||||
return nil, fmt.Errorf("error decoding YAML: %v", err)
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("error decoding YAML: %v", err))
|
||||
}
|
||||
if patchObj.GetAPIVersion() != f.groupVersion.String() {
|
||||
return nil,
|
||||
|
|
|
|||
25
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go
generated
vendored
25
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go
generated
vendored
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"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"
|
||||
|
|
@ -93,7 +94,7 @@ func (c *typeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, err
|
|||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
t := c.parser.Type(gvk)
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("no corresponding type for %v", gvk)
|
||||
return nil, newNoCorrespondingTypeError(gvk)
|
||||
}
|
||||
return t.FromUnstructured(u)
|
||||
}
|
||||
|
|
@ -108,7 +109,7 @@ func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) {
|
|||
gvk := unstructured.GetObjectKind().GroupVersionKind()
|
||||
t := c.parser.Type(gvk)
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("no corresponding type for %v", gvk)
|
||||
return nil, newNoCorrespondingTypeError(gvk)
|
||||
}
|
||||
return t.FromYAML(typed.YAMLObject(string(from)))
|
||||
}
|
||||
|
|
@ -125,3 +126,23 @@ func valueToObject(value *value.Value) (runtime.Object, error) {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,5 +97,5 @@ func (v *versionConverter) Convert(object typed.TypedValue, version fieldpath.AP
|
|||
|
||||
// IsMissingVersionError
|
||||
func (v *versionConverter) IsMissingVersionError(err error) bool {
|
||||
return runtime.IsNotRegisteredError(err)
|
||||
return runtime.IsNotRegisteredError(err) || isNoCorrespondingTypeError(err)
|
||||
}
|
||||
|
|
|
|||
25
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
25
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
|
|
@ -46,7 +46,7 @@ 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)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
|
@ -59,7 +59,7 @@ func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -72,14 +72,13 @@ func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc
|
|||
}
|
||||
|
||||
trace.Step("About to write a response")
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, 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
|
||||
|
|
@ -109,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()
|
||||
|
|
@ -126,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
|
||||
}
|
||||
|
|
@ -163,7 +162,7 @@ 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)
|
||||
|
|
@ -185,7 +184,7 @@ 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)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -249,7 +248,8 @@ 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)
|
||||
|
|
@ -257,7 +257,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||
}
|
||||
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
|
||||
serveWatch(watcher, scope, req, w, timeout)
|
||||
serveWatch(watcher, scope, outputMediaType, req, w, timeout)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -272,8 +272,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||
}
|
||||
trace.Step("Listing from storage done")
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
|
||||
trace.Step(fmt.Sprintf("Writing http response done (%d items)", 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) {
|
||||
|
|
|
|||
28
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
generated
vendored
28
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
generated
vendored
|
|
@ -47,6 +47,34 @@ 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
|
||||
|
|
|
|||
99
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
generated
vendored
99
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
generated
vendored
|
|
@ -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
|
||||
|
|
@ -139,20 +134,11 @@ 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
|
||||
|
|
@ -180,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
|
||||
|
|
@ -212,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
|
||||
|
|
@ -240,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
|
||||
}
|
||||
|
||||
|
|
@ -257,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
|
||||
}
|
||||
|
||||
|
|
@ -270,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})
|
||||
}
|
||||
|
|
@ -286,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
|
||||
}
|
||||
|
|
|
|||
84
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
84
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
|
|
@ -23,7 +23,7 @@ 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"
|
||||
|
|
@ -55,7 +55,7 @@ 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)
|
||||
|
|
@ -95,7 +95,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -118,6 +118,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PatchOptions"))
|
||||
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
|
|
@ -151,6 +152,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
scope.Resource,
|
||||
scope.Subresource,
|
||||
admission.Create,
|
||||
patchToCreateOptions(options),
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
userInfo)
|
||||
staticUpdateAttributes := admission.NewAttributesRecord(
|
||||
|
|
@ -162,6 +164,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
scope.Resource,
|
||||
scope.Subresource,
|
||||
admission.Update,
|
||||
patchToUpdateOptions(options),
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
userInfo,
|
||||
)
|
||||
|
|
@ -191,12 +194,12 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
subresource: scope.Subresource,
|
||||
dryRun: dryrun.IsDryRun(options.DryRun),
|
||||
|
||||
objectInterfaces: &scope,
|
||||
objectInterfaces: scope,
|
||||
|
||||
hubGroupVersion: scope.HubGroupVersion,
|
||||
|
||||
createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, &scope), scope.Authorizer, createAuthorizerAttributes),
|
||||
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, &scope),
|
||||
createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, scope), scope.Authorizer, createAuthorizerAttributes),
|
||||
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, scope),
|
||||
admissionCheck: mutatingAdmission,
|
||||
|
||||
codec: codec,
|
||||
|
|
@ -235,8 +238,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
if wasCreated {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, status, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,6 +338,15 @@ func (p *jsonPatcher) createNewObject() (runtime.Object, error) {
|
|||
func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
|
||||
switch p.patchType {
|
||||
case types.JSONPatchType:
|
||||
// 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())
|
||||
|
|
@ -351,6 +362,15 @@ func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr
|
|||
}
|
||||
return patchedJS, nil
|
||||
case types.MergePatchType:
|
||||
// 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
|
||||
|
|
@ -490,9 +510,9 @@ func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object)
|
|||
return objToUpdate, nil
|
||||
}
|
||||
|
||||
func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation) admission.Attributes {
|
||||
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, p.dryRun, userInfo)
|
||||
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,
|
||||
|
|
@ -501,23 +521,26 @@ func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime
|
|||
func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) {
|
||||
p.trace.Step("About to check admission control")
|
||||
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)
|
||||
attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation, options)
|
||||
return patchedObject, p.admissionCheck.Admit(attributes, p.objectInterfaces)
|
||||
}
|
||||
return patchedObject, nil
|
||||
}
|
||||
|
||||
// patchResource divides PatchResource for easier unit testing
|
||||
func (p *patcher) patchResource(ctx context.Context, scope RequestScope) (runtime.Object, bool, 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:
|
||||
|
|
@ -552,11 +575,8 @@ func (p *patcher) patchResource(ctx context.Context, scope RequestScope) (runtim
|
|||
wasCreated := false
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
|
||||
result, err := finishRequest(p.timeout, func() (runtime.Object, error) {
|
||||
// TODO: Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate
|
||||
options, err := patchToUpdateOptions(p.options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 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
|
||||
|
|
@ -601,12 +621,28 @@ func interpretStrategicMergePatchError(err error) error {
|
|||
}
|
||||
}
|
||||
|
||||
func patchToUpdateOptions(po *metav1.PatchOptions) (*metav1.UpdateOptions, error) {
|
||||
b, err := json.Marshal(po)
|
||||
if err != nil {
|
||||
return nil, 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{}
|
||||
err = json.Unmarshal(b, &uo)
|
||||
return &uo, err
|
||||
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
|
||||
}
|
||||
|
|
|
|||
252
vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
generated
vendored
252
vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
generated
vendored
|
|
@ -26,86 +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, mediaType negotiation.MediaTypeOptions, result runtime.Object) {
|
||||
// status objects are ignored for transformation
|
||||
if _, ok := result.(*metav1.Status); ok {
|
||||
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// ensure the self link and empty list array are set
|
||||
if err := setObjectSelfLink(ctx, result, req, scope.Namer); err != nil {
|
||||
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 {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace := scope.Trace
|
||||
|
||||
// If conversion was allowed by the scope, perform it before writing the response
|
||||
switch target := mediaType.Convert; {
|
||||
|
||||
case target == nil:
|
||||
trace.Step("Writing response")
|
||||
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
|
||||
case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
partial, err := asV1Beta1PartialObjectMetadata(result)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeMetaInternalVersion(partial, statusCode, w, req, &scope, target.GroupVersion()); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
trace.Step("Processing list items")
|
||||
partial, err := asV1Beta1PartialObjectMetadataList(result)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeMetaInternalVersion(partial, statusCode, w, req, &scope, target.GroupVersion()); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
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
|
||||
}
|
||||
|
||||
table, err := asV1Beta1Table(ctx, result, opts, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeMetaInternalVersion(table, statusCode, w, req, &scope, target.GroupVersion()); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
// this block should only be hit if scope AllowsConversion is incorrect
|
||||
accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
|
||||
err := negotiation.NewNotAcceptableError(accepted)
|
||||
obj, err := transformObject(ctx, result, options, mediaType, scope, req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
kind, serializer, _ := targetEncodingForTransform(scope, mediaType, req)
|
||||
responsewriters.WriteObjectNegotiated(serializer, scope, kind.GroupVersion(), w, req, statusCode, obj)
|
||||
}
|
||||
|
||||
// errNotAcceptable indicates Accept negotiation has failed
|
||||
|
|
@ -130,38 +141,41 @@ func (e errNotAcceptable) Status() metav1.Status {
|
|||
}
|
||||
}
|
||||
|
||||
func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta1.TableOptions, scope RequestScope) (runtime.Object, error) {
|
||||
trace := scope.Trace
|
||||
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))
|
||||
}
|
||||
|
||||
trace.Step("Converting to table")
|
||||
table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
|
||||
obj, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trace.Step("Processing rows")
|
||||
table := (*metav1.Table)(obj)
|
||||
|
||||
for i := range table.Rows {
|
||||
item := &table.Rows[i]
|
||||
switch opts.IncludeObject {
|
||||
case metav1beta1.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 metav1beta1.IncludeMetadata, "":
|
||||
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(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata"))
|
||||
item.Object.Object = partial
|
||||
case metav1beta1.IncludeNone:
|
||||
case metav1.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))
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -170,50 +184,74 @@ func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta
|
|||
return table, nil
|
||||
}
|
||||
|
||||
func asV1Beta1PartialObjectMetadata(result runtime.Object) (runtime.Object, error) {
|
||||
func asPartialObjectMetadata(result runtime.Object, groupVersion schema.GroupVersion) (runtime.Object, error) {
|
||||
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))
|
||||
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(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata"))
|
||||
return partial, nil
|
||||
}
|
||||
|
||||
func asV1Beta1PartialObjectMetadataList(result runtime.Object) (runtime.Object, error) {
|
||||
if !meta.IsListType(result) {
|
||||
// TODO: this should be calculated earlier
|
||||
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))
|
||||
}
|
||||
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(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
list.Items = append(list.Items, partial)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func writeMetaInternalVersion(obj runtime.Object, statusCode int, w http.ResponseWriter, req *http.Request, restrictions negotiation.EndpointRestrictions, target schema.GroupVersion) error {
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, restrictions)
|
||||
if err != nil {
|
||||
return err
|
||||
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))
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, target)
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, obj)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
33
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
33
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
|
|
@ -55,23 +55,6 @@ func (w httpResponseWriterWithInit) Write(b []byte) (n int, err error) {
|
|||
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, metrics.APIServerComponent, 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).
|
||||
|
|
@ -123,9 +106,17 @@ func SerializeObject(mediaType string, encoder runtime.Encoder, innerW http.Resp
|
|||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -162,7 +153,7 @@ 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
|
||||
}
|
||||
|
||||
|
|
|
|||
58
vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
generated
vendored
58
vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
generated
vendored
|
|
@ -25,7 +25,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
|
@ -42,7 +41,6 @@ import (
|
|||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/klog"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// RequestScope encapsulates common fields across all RESTful handler methods.
|
||||
|
|
@ -52,13 +50,19 @@ type RequestScope struct {
|
|||
Serializer runtime.NegotiatedSerializer
|
||||
runtime.ParameterCodec
|
||||
|
||||
// StandardSerializers, if set, restricts which serializers can be used when
|
||||
// we aren't transforming the output (into Table or PartialObjectMetadata).
|
||||
// Used only by CRDs which do not yet support Protobuf.
|
||||
StandardSerializers []runtime.SerializerInfo
|
||||
|
||||
Creater runtime.ObjectCreater
|
||||
Convertor runtime.ObjectConvertor
|
||||
Defaulter runtime.ObjectDefaulter
|
||||
Typer runtime.ObjectTyper
|
||||
UnsafeConvertor runtime.ObjectConvertor
|
||||
Authorizer authorizer.Authorizer
|
||||
Trace *utiltrace.Trace
|
||||
|
||||
EquivalentResourceMapper runtime.EquivalentResourceMapper
|
||||
|
||||
TableConvertor rest.TableConvertor
|
||||
FieldManager *fieldmanager.FieldManager
|
||||
|
|
@ -79,12 +83,28 @@ func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Reque
|
|||
responsewriters.ErrorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req)
|
||||
}
|
||||
|
||||
func (scope *RequestScope) AllowsConversion(gvk schema.GroupVersionKind) bool {
|
||||
func (scope *RequestScope) AllowsMediaTypeTransform(mimeType, mimeSubType string, gvk *schema.GroupVersionKind) bool {
|
||||
// some handlers like CRDs can't serve all the mime types that PartialObjectMetadata or Table can - if
|
||||
// gvk is nil (no conversion) allow StandardSerializers to further restrict the set of mime types.
|
||||
if gvk == nil {
|
||||
if len(scope.StandardSerializers) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, info := range scope.StandardSerializers {
|
||||
if info.MediaTypeType == mimeType && info.MediaTypeSubType == mimeSubType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: this is temporary, replace with an abstraction calculated at endpoint installation time
|
||||
if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion {
|
||||
if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion || gvk.GroupVersion() == metav1.SchemeGroupVersion {
|
||||
switch gvk.Kind {
|
||||
case "Table":
|
||||
return scope.TableConvertor != nil
|
||||
return scope.TableConvertor != nil &&
|
||||
mimeType == "application" &&
|
||||
(mimeSubType == "json" || mimeSubType == "yaml")
|
||||
case "PartialObjectMetadata", "PartialObjectMetadataList":
|
||||
// TODO: should delineate between lists and non-list endpoints
|
||||
return true
|
||||
|
|
@ -109,9 +129,12 @@ func (r *RequestScope) GetObjectCreater() runtime.ObjectCreater { return r.C
|
|||
func (r *RequestScope) GetObjectTyper() runtime.ObjectTyper { return r.Typer }
|
||||
func (r *RequestScope) GetObjectDefaulter() runtime.ObjectDefaulter { return r.Defaulter }
|
||||
func (r *RequestScope) GetObjectConvertor() runtime.ObjectConvertor { return r.Convertor }
|
||||
func (r *RequestScope) GetEquivalentResourceMapper() runtime.EquivalentResourceMapper {
|
||||
return r.EquivalentResourceMapper
|
||||
}
|
||||
|
||||
// ConnectResource returns a function that handles a connect request on a rest.Storage object.
|
||||
func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc {
|
||||
func ConnectResource(connecter rest.Connecter, scope *RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
if isDryRun(req.URL) {
|
||||
scope.err(errors.NewBadRequest("dryRun is not supported"), w, req)
|
||||
|
|
@ -138,14 +161,14 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi
|
|||
userInfo, _ := request.UserFrom(ctx)
|
||||
// TODO: remove the mutating admission here as soon as we have ported all plugin that handle CONNECT
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
err = mutatingAdmission.Admit(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo), &scope)
|
||||
err = mutatingAdmission.Admit(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, nil, false, userInfo), scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
err = validatingAdmission.Validate(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo), &scope)
|
||||
err = validatingAdmission.Validate(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, nil, false, userInfo), scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -166,13 +189,13 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi
|
|||
|
||||
// responder implements rest.Responder for assisting a connector in writing objects or errors.
|
||||
type responder struct {
|
||||
scope RequestScope
|
||||
scope *RequestScope
|
||||
req *http.Request
|
||||
w http.ResponseWriter
|
||||
}
|
||||
|
||||
func (r *responder) Object(statusCode int, obj runtime.Object) {
|
||||
responsewriters.WriteObject(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.w, r.req)
|
||||
responsewriters.WriteObjectNegotiated(r.scope.Serializer, r.scope, r.scope.Kind.GroupVersion(), r.w, r.req, statusCode, obj)
|
||||
}
|
||||
|
||||
func (r *responder) Error(err error) {
|
||||
|
|
@ -195,10 +218,12 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
|
|||
defer func() {
|
||||
panicReason := recover()
|
||||
if panicReason != nil {
|
||||
// Same as stdlib http server code. Manually allocate stack
|
||||
// trace buffer size to prevent excessively large logs
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:goruntime.Stack(buf, false)]
|
||||
panicReason = strings.TrimSuffix(fmt.Sprintf("%v\n%s", panicReason, string(buf)), "\n")
|
||||
panicReason = fmt.Sprintf("%v\n%s", panicReason, buf)
|
||||
// Propagate to parent goroutine
|
||||
panicCh <- panicReason
|
||||
}
|
||||
|
|
@ -228,11 +253,11 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
|
|||
}
|
||||
}
|
||||
|
||||
// transformDecodeError adds additional information when a decode fails.
|
||||
// transformDecodeError adds additional information into a bad-request api error when a decode fails.
|
||||
func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, gvk *schema.GroupVersionKind, body []byte) error {
|
||||
objGVKs, _, err := typer.ObjectKinds(into)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.NewBadRequest(err.Error())
|
||||
}
|
||||
objGVK := objGVKs[0]
|
||||
if gvk != nil && len(gvk.Kind) > 0 {
|
||||
|
|
@ -290,7 +315,12 @@ func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) err
|
|||
}
|
||||
|
||||
// setObjectSelfLink sets the self link of an object as needed.
|
||||
// TODO: remove the need for the namer LinkSetters by requiring objects implement either Object or List
|
||||
// interfaces
|
||||
func setObjectSelfLink(ctx context.Context, obj runtime.Object, req *http.Request, namer ScopeNamer) error {
|
||||
// We only generate list links on objects that implement ListInterface - historically we duck typed this
|
||||
// check via reflection, but as we move away from reflection we require that you not only carry Items but
|
||||
// ListMeta into order to be identified as a list.
|
||||
if !meta.IsListType(obj) {
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
|
|
|
|||
29
vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
generated
vendored
29
vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
generated
vendored
|
|
@ -42,7 +42,7 @@ import (
|
|||
)
|
||||
|
||||
// UpdateResource returns a function that will handle a resource update
|
||||
func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
// For performance tracking purposes.
|
||||
trace := utiltrace.New("Update " + req.URL.Path)
|
||||
|
|
@ -64,7 +64,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -87,6 +87,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("UpdateOptions"))
|
||||
|
||||
s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
|
||||
if err != nil {
|
||||
|
|
@ -138,11 +139,11 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
return nil, fmt.Errorf("unexpected error when extracting UID from oldObj: %v", err.Error())
|
||||
} else if !isNotZeroObject {
|
||||
if mutatingAdmission.Handles(admission.Create) {
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo), &scope)
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, updateToCreateOptions(options), dryrun.IsDryRun(options.DryRun), userInfo), scope)
|
||||
}
|
||||
} else {
|
||||
if mutatingAdmission.Handles(admission.Update) {
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo), &scope)
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, options, dryrun.IsDryRun(options.DryRun), userInfo), scope)
|
||||
}
|
||||
}
|
||||
return newObj, nil
|
||||
|
|
@ -172,11 +173,11 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
rest.DefaultUpdatedObjectInfo(obj, transformers...),
|
||||
withAuthorization(rest.AdmissionToValidateObjectFunc(
|
||||
admit,
|
||||
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo), &scope),
|
||||
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, updateToCreateOptions(options), dryrun.IsDryRun(options.DryRun), userInfo), scope),
|
||||
scope.Authorizer, createAuthorizerAttributes),
|
||||
rest.AdmissionToValidateObjectUpdateFunc(
|
||||
admit,
|
||||
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo), &scope),
|
||||
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, options, dryrun.IsDryRun(options.DryRun), userInfo), scope),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
|
|
@ -194,8 +195,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
status = http.StatusCreated
|
||||
}
|
||||
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
|
||||
transformResponseObject(ctx, scope, trace, req, w, status, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -230,3 +230,16 @@ func withAuthorization(validate rest.ValidateObjectFunc, a authorizer.Authorizer
|
|||
return errors.NewForbidden(gr, name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// updateToCreateOptions creates a CreateOptions with the same field values as the provided UpdateOptions.
|
||||
func updateToCreateOptions(uo *metav1.UpdateOptions) *metav1.CreateOptions {
|
||||
if uo == nil {
|
||||
return nil
|
||||
}
|
||||
co := &metav1.CreateOptions{
|
||||
DryRun: uo.DryRun,
|
||||
FieldManager: uo.FieldManager,
|
||||
}
|
||||
co.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
|
||||
return co
|
||||
}
|
||||
|
|
|
|||
76
vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
generated
vendored
76
vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
generated
vendored
|
|
@ -25,13 +25,13 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/server/httplog"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
|
||||
|
|
@ -61,42 +61,51 @@ func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) {
|
|||
return t.C, t.Stop
|
||||
}
|
||||
|
||||
// serveWatch handles serving requests to the server
|
||||
// serveWatch will serve a watch response.
|
||||
// TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled.
|
||||
func serveWatch(watcher watch.Interface, scope RequestScope, req *http.Request, w http.ResponseWriter, timeout time.Duration) {
|
||||
// negotiate for the stream serializer
|
||||
serializer, err := negotiation.NegotiateOutputStreamSerializer(req, scope.Serializer)
|
||||
func serveWatch(watcher watch.Interface, scope *RequestScope, mediaTypeOptions negotiation.MediaTypeOptions, req *http.Request, w http.ResponseWriter, timeout time.Duration) {
|
||||
options, err := optionsForTransform(mediaTypeOptions, req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// negotiate for the stream serializer from the scope's serializer
|
||||
serializer, err := negotiation.NegotiateOutputMediaTypeStream(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
framer := serializer.StreamSerializer.Framer
|
||||
streamSerializer := serializer.StreamSerializer.Serializer
|
||||
embedded := serializer.Serializer
|
||||
encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion())
|
||||
useTextFraming := serializer.EncodesAsText
|
||||
if framer == nil {
|
||||
scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), w, req)
|
||||
return
|
||||
}
|
||||
encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion())
|
||||
|
||||
useTextFraming := serializer.EncodesAsText
|
||||
|
||||
// find the embedded serializer matching the media type
|
||||
embeddedEncoder := scope.Serializer.EncoderForVersion(embedded, scope.Kind.GroupVersion())
|
||||
|
||||
// TODO: next step, get back mediaTypeOptions from negotiate and return the exact value here
|
||||
mediaType := serializer.MediaType
|
||||
if mediaType != runtime.ContentTypeJSON {
|
||||
mediaType += ";stream=watch"
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
// locate the appropriate embedded encoder based on the transform
|
||||
var embeddedEncoder runtime.Encoder
|
||||
contentKind, contentSerializer, transform := targetEncodingForTransform(scope, mediaTypeOptions, req)
|
||||
if transform {
|
||||
info, ok := runtime.SerializerInfoForMediaType(contentSerializer.SupportedMediaTypes(), serializer.MediaType)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("no encoder for %q exists in the requested target %#v", serializer.MediaType, contentSerializer), w, req)
|
||||
return
|
||||
}
|
||||
embeddedEncoder = contentSerializer.EncoderForVersion(info.Serializer, contentKind.GroupVersion())
|
||||
} else {
|
||||
embeddedEncoder = scope.Serializer.EncoderForVersion(serializer.Serializer, contentKind.GroupVersion())
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
|
||||
server := &WatchServer{
|
||||
Watching: watcher,
|
||||
Scope: scope,
|
||||
|
|
@ -106,10 +115,20 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *http.Request,
|
|||
Framer: framer,
|
||||
Encoder: encoder,
|
||||
EmbeddedEncoder: embeddedEncoder,
|
||||
Fixup: func(obj runtime.Object) {
|
||||
if err := setSelfLink(obj, requestInfo, scope.Namer); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to set link for object %v: %v", reflect.TypeOf(obj), err))
|
||||
|
||||
Fixup: func(obj runtime.Object) runtime.Object {
|
||||
result, err := transformObject(ctx, obj, options, mediaTypeOptions, scope, req)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to transform object %v: %v", reflect.TypeOf(obj), err))
|
||||
return obj
|
||||
}
|
||||
// When we are transformed to a table, use the table options as the state for whether we
|
||||
// should print headers - on watch, we only want to print table headers on the first object
|
||||
// and omit them on subsequent events.
|
||||
if tableOptions, ok := options.(*metav1beta1.TableOptions); ok {
|
||||
tableOptions.NoHeaders = true
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
TimeoutFactory: &realTimeoutFactory{timeout},
|
||||
|
|
@ -121,7 +140,7 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *http.Request,
|
|||
// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
|
||||
type WatchServer struct {
|
||||
Watching watch.Interface
|
||||
Scope RequestScope
|
||||
Scope *RequestScope
|
||||
|
||||
// true if websocket messages should use text framing (as opposed to binary framing)
|
||||
UseTextFraming bool
|
||||
|
|
@ -133,7 +152,8 @@ type WatchServer struct {
|
|||
Encoder runtime.Encoder
|
||||
// used to encode the nested object in the watch stream
|
||||
EmbeddedEncoder runtime.Encoder
|
||||
Fixup func(runtime.Object)
|
||||
// used to correct the object before we send it to the serializer
|
||||
Fixup func(runtime.Object) runtime.Object
|
||||
|
||||
TimeoutFactory TimeoutFactory
|
||||
}
|
||||
|
|
@ -191,6 +211,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
var unknown runtime.Unknown
|
||||
internalEvent := &metav1.InternalEvent{}
|
||||
outEvent := &metav1.WatchEvent{}
|
||||
buf := &bytes.Buffer{}
|
||||
ch := s.Watching.ResultChan()
|
||||
for {
|
||||
|
|
@ -205,8 +226,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
obj := event.Object
|
||||
s.Fixup(obj)
|
||||
obj := s.Fixup(event.Object)
|
||||
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||
// unexpected error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err))
|
||||
|
|
@ -218,10 +238,11 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
unknown.Raw = buf.Bytes()
|
||||
event.Object = &unknown
|
||||
|
||||
*outEvent = metav1.WatchEvent{}
|
||||
|
||||
// create the external type directly and encode it. Clients will only recognize the serialization we provide.
|
||||
// The internal event is being reused, not reallocated so its just a few extra assignments to do it this way
|
||||
// and we get the benefit of using conversion functions which already have to stay in sync
|
||||
outEvent := &metav1.WatchEvent{}
|
||||
*internalEvent = metav1.InternalEvent(event)
|
||||
err := metav1.Convert_v1_InternalEvent_To_v1_WatchEvent(internalEvent, outEvent, nil)
|
||||
if err != nil {
|
||||
|
|
@ -272,8 +293,7 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) {
|
|||
// End of results.
|
||||
return
|
||||
}
|
||||
obj := event.Object
|
||||
s.Fixup(obj)
|
||||
obj := s.Fixup(event.Object)
|
||||
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||
// unexpected error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err))
|
||||
|
|
|
|||
37
vendor/k8s.io/apiserver/pkg/endpoints/installer.go
generated
vendored
37
vendor/k8s.io/apiserver/pkg/endpoints/installer.go
generated
vendored
|
|
@ -512,6 +512,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
//
|
||||
// test/integration/auth_test.go is currently the most comprehensive status code test
|
||||
|
||||
for _, s := range a.group.Serializer.SupportedMediaTypes() {
|
||||
if len(s.MediaTypeSubType) == 0 || len(s.MediaTypeType) == 0 {
|
||||
return nil, fmt.Errorf("all serializers in the group Serializer must have MediaTypeType and MediaTypeSubType set: %s", s.MediaType)
|
||||
}
|
||||
}
|
||||
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
|
||||
allMediaTypes := append(mediaTypes, streamMediaTypes...)
|
||||
ws.Produces(allMediaTypes...)
|
||||
|
|
@ -527,6 +532,8 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
UnsafeConvertor: a.group.UnsafeConvertor,
|
||||
Authorizer: a.group.Authorizer,
|
||||
|
||||
EquivalentResourceMapper: a.group.EquivalentResourceRegistry,
|
||||
|
||||
// TODO: Check for the interface on storage
|
||||
TableConvertor: tableProvider,
|
||||
|
||||
|
|
@ -801,6 +808,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
|
||||
Writes(versionedStatus).
|
||||
Returns(http.StatusOK, "OK", versionedStatus)
|
||||
if isCollectionDeleter {
|
||||
route.Reads(versionedDeleterObject)
|
||||
route.ParameterNamed("body").Required(false)
|
||||
if err := AddObjectParams(ws, route, versionedDeleteOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -913,6 +927,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
apiResource.Kind = gvk.Kind
|
||||
}
|
||||
|
||||
// Record the existence of the GVR and the corresponding GVK
|
||||
a.group.EquivalentResourceRegistry.RegisterKindFor(reqScope.Resource, reqScope.Subresource, fqKindToRegister)
|
||||
|
||||
return &apiResource, nil
|
||||
}
|
||||
|
||||
|
|
@ -1071,60 +1088,60 @@ func isVowel(c rune) bool {
|
|||
|
||||
func restfulListResource(r rest.Lister, rw rest.Watcher, scope handlers.RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.ListResource(r, rw, scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request)
|
||||
handlers.ListResource(r, rw, &scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulCreateNamedResource(r rest.NamedCreater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.CreateNamedResource(r, scope, admit)(res.ResponseWriter, req.Request)
|
||||
handlers.CreateNamedResource(r, &scope, admit)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.CreateResource(r, scope, admit)(res.ResponseWriter, req.Request)
|
||||
handlers.CreateResource(r, &scope, admit)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulDeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.DeleteResource(r, allowsOptions, scope, admit)(res.ResponseWriter, req.Request)
|
||||
handlers.DeleteResource(r, allowsOptions, &scope, admit)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulDeleteCollection(r rest.CollectionDeleter, checkBody bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.DeleteCollection(r, checkBody, scope, admit)(res.ResponseWriter, req.Request)
|
||||
handlers.DeleteCollection(r, checkBody, &scope, admit)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulUpdateResource(r rest.Updater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.UpdateResource(r, scope, admit)(res.ResponseWriter, req.Request)
|
||||
handlers.UpdateResource(r, &scope, admit)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulPatchResource(r rest.Patcher, scope handlers.RequestScope, admit admission.Interface, supportedTypes []string) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.PatchResource(r, scope, admit, supportedTypes)(res.ResponseWriter, req.Request)
|
||||
handlers.PatchResource(r, &scope, admit, supportedTypes)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulGetResource(r rest.Getter, e rest.Exporter, scope handlers.RequestScope) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.GetResource(r, e, scope)(res.ResponseWriter, req.Request)
|
||||
handlers.GetResource(r, e, &scope)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulGetResourceWithOptions(r rest.GetterWithOptions, scope handlers.RequestScope, isSubresource bool) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.GetResourceWithOptions(r, scope, isSubresource)(res.ResponseWriter, req.Request)
|
||||
handlers.GetResourceWithOptions(r, &scope, isSubresource)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func restfulConnectResource(connecter rest.Connecter, scope handlers.RequestScope, admit admission.Interface, restPath string, isSubresource bool) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.ConnectResource(connecter, scope, admit, restPath, isSubresource)(res.ResponseWriter, req.Request)
|
||||
handlers.ConnectResource(connecter, &scope, admit, restPath, isSubresource)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
generated
vendored
19
vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
generated
vendored
|
|
@ -20,6 +20,7 @@ import (
|
|||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -34,7 +35,7 @@ import (
|
|||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
restful "github.com/emicklei/go-restful"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
|
|
@ -234,7 +235,7 @@ func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, comp
|
|||
// a request. verb must be uppercase to be backwards compatible with existing monitoring tooling.
|
||||
func MonitorRequest(req *http.Request, verb, group, version, resource, subresource, scope, component, contentType string, httpCode, respSize int, elapsed time.Duration) {
|
||||
reportedVerb := cleanVerb(verb, req)
|
||||
dryRun := cleanDryRun(req.URL.Query()["dryRun"])
|
||||
dryRun := cleanDryRun(req.URL)
|
||||
client := cleanUserAgent(utilnet.GetHTTPClient(req))
|
||||
elapsedMicroseconds := float64(elapsed / time.Microsecond)
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
|
|
@ -331,12 +332,19 @@ func cleanVerb(verb string, request *http.Request) string {
|
|||
return reportedVerb
|
||||
}
|
||||
|
||||
func cleanDryRun(dryRun []string) string {
|
||||
func cleanDryRun(u *url.URL) string {
|
||||
// avoid allocating when we don't see dryRun in the query
|
||||
if !strings.Contains(u.RawQuery, "dryRun") {
|
||||
return ""
|
||||
}
|
||||
dryRun := u.Query()["dryRun"]
|
||||
if errs := validation.ValidateDryRun(nil, dryRun); len(errs) > 0 {
|
||||
return "invalid"
|
||||
}
|
||||
// Since dryRun could be valid with any arbitrarily long length
|
||||
// we have to dedup and sort the elements before joining them together
|
||||
// TODO: this is a fairly large allocation for what it does, consider
|
||||
// a sort and dedup in a single pass
|
||||
return strings.Join(utilsets.NewString(dryRun...).List(), ",")
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +354,10 @@ func cleanUserAgent(ua string) string {
|
|||
return "Browser"
|
||||
}
|
||||
// If an old "kubectl.exe" has passed us its full path, we discard the path portion.
|
||||
ua = kubectlExeRegexp.ReplaceAllString(ua, "$1")
|
||||
if kubectlExeRegexp.MatchString(ua) {
|
||||
// avoid an allocation
|
||||
ua = kubectlExeRegexp.ReplaceAllString(ua, "$1")
|
||||
}
|
||||
return ua
|
||||
}
|
||||
|
||||
|
|
|
|||
75
vendor/k8s.io/apiserver/pkg/features/kube_features.go
generated
vendored
75
vendor/k8s.io/apiserver/pkg/features/kube_features.go
generated
vendored
|
|
@ -17,7 +17,10 @@ limitations under the License.
|
|||
package features
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -33,7 +36,7 @@ const (
|
|||
//
|
||||
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
|
||||
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
|
||||
StreamingProxyRedirects utilfeature.Feature = "StreamingProxyRedirects"
|
||||
StreamingProxyRedirects featuregate.Feature = "StreamingProxyRedirects"
|
||||
|
||||
// owner: @tallclair
|
||||
// alpha: v1.10
|
||||
|
|
@ -41,7 +44,7 @@ const (
|
|||
//
|
||||
// ValidateProxyRedirects controls whether the apiserver should validate that redirects are only
|
||||
// followed to the same host. Only used if StreamingProxyRedirects is enabled.
|
||||
ValidateProxyRedirects utilfeature.Feature = "ValidateProxyRedirects"
|
||||
ValidateProxyRedirects featuregate.Feature = "ValidateProxyRedirects"
|
||||
|
||||
// owner: @tallclair
|
||||
// alpha: v1.7
|
||||
|
|
@ -51,20 +54,20 @@ const (
|
|||
// AdvancedAuditing enables a much more general API auditing pipeline, which includes support for
|
||||
// pluggable output backends and an audit policy specifying how different requests should be
|
||||
// audited.
|
||||
AdvancedAuditing utilfeature.Feature = "AdvancedAuditing"
|
||||
AdvancedAuditing featuregate.Feature = "AdvancedAuditing"
|
||||
|
||||
// owner: @pbarker
|
||||
// alpha: v1.13
|
||||
//
|
||||
// DynamicAuditing enables configuration of audit policy and webhook backends through an
|
||||
// AuditSink API object.
|
||||
DynamicAuditing utilfeature.Feature = "DynamicAuditing"
|
||||
DynamicAuditing featuregate.Feature = "DynamicAuditing"
|
||||
|
||||
// owner: @ilackams
|
||||
// alpha: v1.7
|
||||
//
|
||||
// Enables compression of REST responses (GET and LIST only)
|
||||
APIResponseCompression utilfeature.Feature = "APIResponseCompression"
|
||||
APIResponseCompression featuregate.Feature = "APIResponseCompression"
|
||||
|
||||
// owner: @smarterclayton
|
||||
// alpha: v1.8
|
||||
|
|
@ -72,7 +75,7 @@ const (
|
|||
//
|
||||
// Allow API clients to retrieve resource lists in chunks rather than
|
||||
// all at once.
|
||||
APIListChunking utilfeature.Feature = "APIListChunking"
|
||||
APIListChunking featuregate.Feature = "APIListChunking"
|
||||
|
||||
// owner: @apelisse
|
||||
// alpha: v1.12
|
||||
|
|
@ -81,51 +84,75 @@ const (
|
|||
// Allow requests to be processed but not stored, so that
|
||||
// validation, merging, mutation can be tested without
|
||||
// committing.
|
||||
DryRun utilfeature.Feature = "DryRun"
|
||||
DryRun featuregate.Feature = "DryRun"
|
||||
|
||||
// owner: @caesarxuchao
|
||||
// alpha: v1.15
|
||||
//
|
||||
// Allow apiservers to show a count of remaining items in the response
|
||||
// to a chunking list request.
|
||||
RemainingItemCount featuregate.Feature = "RemainingItemCount"
|
||||
|
||||
// owner: @apelisse, @lavalamp
|
||||
// alpha: v1.14
|
||||
//
|
||||
// Server-side apply. Merging happens on the server.
|
||||
ServerSideApply utilfeature.Feature = "ServerSideApply"
|
||||
ServerSideApply featuregate.Feature = "ServerSideApply"
|
||||
|
||||
// owner: @caesarxuchao
|
||||
// alpha: v1.14
|
||||
// beta: v1.15
|
||||
//
|
||||
// Allow apiservers to expose the storage version hash in the discovery
|
||||
// document.
|
||||
StorageVersionHash utilfeature.Feature = "StorageVersionHash"
|
||||
StorageVersionHash featuregate.Feature = "StorageVersionHash"
|
||||
|
||||
// owner: @ksubrmnn
|
||||
// alpha: v1.14
|
||||
//
|
||||
// Allows kube-proxy to run in Overlay mode for Windows
|
||||
WinOverlay utilfeature.Feature = "WinOverlay"
|
||||
WinOverlay featuregate.Feature = "WinOverlay"
|
||||
|
||||
// owner: @ksubrmnn
|
||||
// alpha: v1.14
|
||||
//
|
||||
// Allows kube-proxy to create DSR loadbalancers for Windows
|
||||
WinDSR utilfeature.Feature = "WinDSR"
|
||||
WinDSR featuregate.Feature = "WinDSR"
|
||||
|
||||
// owner: @wojtek-t
|
||||
// alpha: v1.15
|
||||
//
|
||||
// Enables support for watch bookmark events.
|
||||
WatchBookmark featuregate.Feature = "WatchBookmark"
|
||||
|
||||
// owner: @MikeSpreitzer @yue9944882
|
||||
// alpha: v1.15
|
||||
//
|
||||
//
|
||||
// Enables managing request concurrency with prioritization and fairness at each server
|
||||
RequestManagement featuregate.Feature = "RequestManagement"
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates)
|
||||
runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates))
|
||||
}
|
||||
|
||||
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.
|
||||
// To add a new feature, define a key for it above and add it here. The features will be
|
||||
// available throughout Kubernetes binaries.
|
||||
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
|
||||
StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
|
||||
ValidateProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
|
||||
AdvancedAuditing: {Default: true, PreRelease: utilfeature.GA},
|
||||
DynamicAuditing: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
APIResponseCompression: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
APIListChunking: {Default: true, PreRelease: utilfeature.Beta},
|
||||
DryRun: {Default: true, PreRelease: utilfeature.Beta},
|
||||
ServerSideApply: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
StorageVersionHash: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
WinOverlay: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
WinDSR: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
StreamingProxyRedirects: {Default: true, PreRelease: featuregate.Beta},
|
||||
ValidateProxyRedirects: {Default: true, PreRelease: featuregate.Beta},
|
||||
AdvancedAuditing: {Default: true, PreRelease: featuregate.GA},
|
||||
DynamicAuditing: {Default: false, PreRelease: featuregate.Alpha},
|
||||
APIResponseCompression: {Default: false, PreRelease: featuregate.Alpha},
|
||||
APIListChunking: {Default: true, PreRelease: featuregate.Beta},
|
||||
DryRun: {Default: true, PreRelease: featuregate.Beta},
|
||||
RemainingItemCount: {Default: false, PreRelease: featuregate.Alpha},
|
||||
ServerSideApply: {Default: false, PreRelease: featuregate.Alpha},
|
||||
StorageVersionHash: {Default: true, PreRelease: featuregate.Beta},
|
||||
WinOverlay: {Default: false, PreRelease: featuregate.Alpha},
|
||||
WinDSR: {Default: false, PreRelease: featuregate.Alpha},
|
||||
WatchBookmark: {Default: false, PreRelease: featuregate.Alpha},
|
||||
RequestManagement: {Default: false, PreRelease: featuregate.Alpha},
|
||||
}
|
||||
|
|
|
|||
10
vendor/k8s.io/apiserver/pkg/registry/generic/registry/decorated_watcher.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/registry/generic/registry/decorated_watcher.go
generated
vendored
|
|
@ -45,11 +45,17 @@ func newDecoratedWatcher(w watch.Interface, decorator ObjectFunc) *decoratedWatc
|
|||
|
||||
func (d *decoratedWatcher) run(ctx context.Context) {
|
||||
var recv, send watch.Event
|
||||
var ok bool
|
||||
for {
|
||||
select {
|
||||
case recv = <-d.w.ResultChan():
|
||||
case recv, ok = <-d.w.ResultChan():
|
||||
// The underlying channel may be closed after timeout.
|
||||
if !ok {
|
||||
d.cancel()
|
||||
return
|
||||
}
|
||||
switch recv.Type {
|
||||
case watch.Added, watch.Modified, watch.Deleted:
|
||||
case watch.Added, watch.Modified, watch.Deleted, watch.Bookmark:
|
||||
err := d.decorator(recv.Object)
|
||||
if err != nil {
|
||||
send = makeStatusErrorEvent(err)
|
||||
|
|
|
|||
9
vendor/k8s.io/apiserver/pkg/registry/generic/registry/dryrun.go
generated
vendored
9
vendor/k8s.io/apiserver/pkg/registry/generic/registry/dryrun.go
generated
vendored
|
|
@ -44,14 +44,17 @@ func (s *DryRunnableStorage) Create(ctx context.Context, key string, obj, out ru
|
|||
return s.Storage.Create(ctx, key, obj, out, ttl)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, dryRun bool) error {
|
||||
func (s *DryRunnableStorage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, deleteValidation storage.ValidateObjectFunc, dryRun bool) error {
|
||||
if dryRun {
|
||||
if err := s.Storage.Get(ctx, key, "", out, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return preconditions.Check(key, out)
|
||||
if err := preconditions.Check(key, out); err != nil {
|
||||
return err
|
||||
}
|
||||
return deleteValidation(out)
|
||||
}
|
||||
return s.Storage.Delete(ctx, key, out, preconditions)
|
||||
return s.Storage.Delete(ctx, key, out, preconditions, deleteValidation)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Watch(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate) (watch.Interface, error) {
|
||||
|
|
|
|||
12
vendor/k8s.io/apiserver/pkg/registry/generic/registry/storage_factory.go
generated
vendored
12
vendor/k8s.io/apiserver/pkg/registry/generic/registry/storage_factory.go
generated
vendored
|
|
@ -34,19 +34,21 @@ import (
|
|||
func StorageWithCacher(capacity int) generic.StorageDecorator {
|
||||
return func(
|
||||
storageConfig *storagebackend.Config,
|
||||
objectType runtime.Object,
|
||||
resourcePrefix string,
|
||||
keyFunc func(obj runtime.Object) (string, error),
|
||||
newFunc func() runtime.Object,
|
||||
newListFunc func() runtime.Object,
|
||||
getAttrsFunc storage.AttrFunc,
|
||||
triggerFunc storage.TriggerPublisherFunc) (storage.Interface, factory.DestroyFunc) {
|
||||
|
||||
s, d := generic.NewRawStorage(storageConfig)
|
||||
if capacity == 0 {
|
||||
klog.V(5).Infof("Storage caching is disabled for %T", objectType)
|
||||
if capacity <= 0 {
|
||||
klog.V(5).Infof("Storage caching is disabled for %T", newFunc())
|
||||
return s, d
|
||||
}
|
||||
klog.V(5).Infof("Storage caching is enabled for %T with capacity %v", objectType, capacity)
|
||||
if klog.V(5) {
|
||||
klog.Infof("Storage caching is enabled for %T with capacity %v", newFunc(), capacity)
|
||||
}
|
||||
|
||||
// TODO: we would change this later to make storage always have cacher and hide low level KV layer inside.
|
||||
// Currently it has two layers of same storage interface -- cacher and low level kv.
|
||||
|
|
@ -54,9 +56,9 @@ func StorageWithCacher(capacity int) generic.StorageDecorator {
|
|||
CacheCapacity: capacity,
|
||||
Storage: s,
|
||||
Versioner: etcdstorage.APIObjectVersioner{},
|
||||
Type: objectType,
|
||||
ResourcePrefix: resourcePrefix,
|
||||
KeyFunc: keyFunc,
|
||||
NewFunc: newFunc,
|
||||
NewListFunc: newListFunc,
|
||||
GetAttrsFunc: getAttrsFunc,
|
||||
TriggerPublisherFunc: triggerFunc,
|
||||
|
|
|
|||
54
vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go
generated
vendored
54
vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go
generated
vendored
|
|
@ -166,6 +166,11 @@ type Store struct {
|
|||
// ReturnDeletedObject determines whether the Store returns the object
|
||||
// that was deleted. Otherwise, return a generic success status response.
|
||||
ReturnDeletedObject bool
|
||||
// ShouldDeleteDuringUpdate is an optional function to determine whether
|
||||
// an update from existing to obj should result in a delete.
|
||||
// If specified, this is checked in addition to standard finalizer,
|
||||
// deletionTimestamp, and deletionGracePeriodSeconds checks.
|
||||
ShouldDeleteDuringUpdate func(ctx context.Context, key string, obj, existing runtime.Object) bool
|
||||
// ExportStrategy implements resource-specific behavior during export,
|
||||
// optional. Exported objects are not decorated.
|
||||
ExportStrategy rest.RESTExportStrategy
|
||||
|
|
@ -388,10 +393,12 @@ func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// shouldDeleteDuringUpdate checks if a Update is removing all the object's
|
||||
// finalizers. If so, it further checks if the object's
|
||||
// DeletionGracePeriodSeconds is 0.
|
||||
func (e *Store) shouldDeleteDuringUpdate(ctx context.Context, key string, obj, existing runtime.Object) bool {
|
||||
// ShouldDeleteDuringUpdate is the default function for
|
||||
// checking if an object should be deleted during an update.
|
||||
// It checks if the new object has no finalizers,
|
||||
// the existing object's deletionTimestamp is set, and
|
||||
// the existing object's deletionGracePeriodSeconds is 0 or nil
|
||||
func ShouldDeleteDuringUpdate(ctx context.Context, key string, obj, existing runtime.Object) bool {
|
||||
newMeta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
|
|
@ -402,7 +409,16 @@ func (e *Store) shouldDeleteDuringUpdate(ctx context.Context, key string, obj, e
|
|||
utilruntime.HandleError(err)
|
||||
return false
|
||||
}
|
||||
return len(newMeta.GetFinalizers()) == 0 && oldMeta.GetDeletionGracePeriodSeconds() != nil && *oldMeta.GetDeletionGracePeriodSeconds() == 0
|
||||
if len(newMeta.GetFinalizers()) > 0 {
|
||||
// don't delete with finalizers remaining in the new object
|
||||
return false
|
||||
}
|
||||
if oldMeta.GetDeletionTimestamp() == nil {
|
||||
// don't delete if the existing object hasn't had a delete request made
|
||||
return false
|
||||
}
|
||||
// delete if the existing object has no grace period or a grace period of 0
|
||||
return oldMeta.GetDeletionGracePeriodSeconds() == nil || *oldMeta.GetDeletionGracePeriodSeconds() == 0
|
||||
}
|
||||
|
||||
// deleteWithoutFinalizers handles deleting an object ignoring its finalizer list.
|
||||
|
|
@ -410,7 +426,8 @@ func (e *Store) shouldDeleteDuringUpdate(ctx context.Context, key string, obj, e
|
|||
func (e *Store) deleteWithoutFinalizers(ctx context.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions, dryRun bool) (runtime.Object, bool, error) {
|
||||
out := e.NewFunc()
|
||||
klog.V(6).Infof("going to delete %s from registry, triggered by update", name)
|
||||
if err := e.Storage.Delete(ctx, key, out, preconditions, dryRun); err != nil {
|
||||
// Using the rest.ValidateAllObjectFunc because the request is an UPDATE request and has already passed the admission for the UPDATE verb.
|
||||
if err := e.Storage.Delete(ctx, key, out, preconditions, rest.ValidateAllObjectFunc, dryRun); err != nil {
|
||||
// Deletion is racy, i.e., there could be multiple update
|
||||
// requests to remove all finalizers from the object, so we
|
||||
// ignore the NotFound error.
|
||||
|
|
@ -533,7 +550,9 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
|
|||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if e.shouldDeleteDuringUpdate(ctx, key, obj, existing) {
|
||||
// Check the default delete-during-update conditions, and store-specific conditions if provided
|
||||
if ShouldDeleteDuringUpdate(ctx, key, obj, existing) &&
|
||||
(e.ShouldDeleteDuringUpdate == nil || e.ShouldDeleteDuringUpdate(ctx, key, obj, existing)) {
|
||||
deleteObj = obj
|
||||
return nil, nil, errEmptiedFinalizers
|
||||
}
|
||||
|
|
@ -782,7 +801,7 @@ func markAsDeleting(obj runtime.Object, now time.Time) (err error) {
|
|||
// should be deleted immediately
|
||||
// 4. a new output object with the state that was updated
|
||||
// 5. a copy of the last existing state of the object
|
||||
func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name, key string, options *metav1.DeleteOptions, preconditions storage.Preconditions, in runtime.Object) (err error, ignoreNotFound, deleteImmediately bool, out, lastExisting runtime.Object) {
|
||||
func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name, key string, options *metav1.DeleteOptions, preconditions storage.Preconditions, deleteValidation rest.ValidateObjectFunc, in runtime.Object) (err error, ignoreNotFound, deleteImmediately bool, out, lastExisting runtime.Object) {
|
||||
lastGraceful := int64(0)
|
||||
var pendingFinalizers bool
|
||||
out = e.NewFunc()
|
||||
|
|
@ -793,6 +812,9 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name
|
|||
false, /* ignoreNotFound */
|
||||
&preconditions,
|
||||
storage.SimpleUpdate(func(existing runtime.Object) (runtime.Object, error) {
|
||||
if err := deleteValidation(existing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, existing, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -863,16 +885,17 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name
|
|||
}
|
||||
|
||||
// Delete removes the item from storage.
|
||||
func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
func (e *Store) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
key, err := e.KeyFunc(ctx, name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
obj := e.NewFunc()
|
||||
qualifiedResource := e.qualifiedResourceFromContext(ctx)
|
||||
if err := e.Storage.Get(ctx, key, "", obj, false); err != nil {
|
||||
if err = e.Storage.Get(ctx, key, "", obj, false); err != nil {
|
||||
return nil, false, storeerr.InterpretDeleteError(err, qualifiedResource, name)
|
||||
}
|
||||
|
||||
// support older consumers of delete by treating "nil" as delete immediately
|
||||
if options == nil {
|
||||
options = metav1.NewDeleteOptions(0)
|
||||
|
|
@ -906,7 +929,7 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO
|
|||
shouldUpdateFinalizers, _ := deletionFinalizersForGarbageCollection(ctx, e, accessor, options)
|
||||
// TODO: remove the check, because we support no-op updates now.
|
||||
if graceful || pendingFinalizers || shouldUpdateFinalizers {
|
||||
err, ignoreNotFound, deleteImmediately, out, lastExisting = e.updateForGracefulDeletionAndFinalizers(ctx, name, key, options, preconditions, obj)
|
||||
err, ignoreNotFound, deleteImmediately, out, lastExisting = e.updateForGracefulDeletionAndFinalizers(ctx, name, key, options, preconditions, deleteValidation, obj)
|
||||
}
|
||||
|
||||
// !deleteImmediately covers all cases where err != nil. We keep both to be future-proof.
|
||||
|
|
@ -929,7 +952,7 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO
|
|||
// delete immediately, or no graceful deletion supported
|
||||
klog.V(6).Infof("going to delete %s from registry: ", name)
|
||||
out = e.NewFunc()
|
||||
if err := e.Storage.Delete(ctx, key, out, &preconditions, dryrun.IsDryRun(options.DryRun)); err != nil {
|
||||
if err := e.Storage.Delete(ctx, key, out, &preconditions, storage.ValidateObjectFunc(deleteValidation), dryrun.IsDryRun(options.DryRun)); err != nil {
|
||||
// Please refer to the place where we set ignoreNotFound for the reason
|
||||
// why we ignore the NotFound error .
|
||||
if storage.IsNotFound(err) && ignoreNotFound && lastExisting != nil {
|
||||
|
|
@ -954,7 +977,7 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO
|
|||
// are removing all objects of a given type) with the current API (it's technically
|
||||
// possibly with storage API, but watch is not delivered correctly then).
|
||||
// It will be possible to fix it with v3 etcd API.
|
||||
func (e *Store) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
func (e *Store) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
if listOptions == nil {
|
||||
listOptions = &metainternalversion.ListOptions{}
|
||||
} else {
|
||||
|
|
@ -1007,7 +1030,7 @@ func (e *Store) DeleteCollection(ctx context.Context, options *metav1.DeleteOpti
|
|||
errs <- err
|
||||
return
|
||||
}
|
||||
if _, _, err := e.Delete(ctx, accessor.GetName(), options); err != nil && !kubeerr.IsNotFound(err) {
|
||||
if _, _, err := e.Delete(ctx, accessor.GetName(), deleteValidation, options); err != nil && !kubeerr.IsNotFound(err) {
|
||||
klog.V(4).Infof("Delete %s in DeleteCollection failed: %v", accessor.GetName(), err)
|
||||
errs <- err
|
||||
return
|
||||
|
|
@ -1075,6 +1098,7 @@ func (e *Store) Watch(ctx context.Context, options *metainternalversion.ListOpti
|
|||
resourceVersion := ""
|
||||
if options != nil {
|
||||
resourceVersion = options.ResourceVersion
|
||||
predicate.AllowWatchBookmarks = options.AllowWatchBookmarks
|
||||
}
|
||||
return e.WatchPredicate(ctx, predicate, resourceVersion)
|
||||
}
|
||||
|
|
@ -1288,9 +1312,9 @@ func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {
|
|||
e.Storage.Codec = opts.StorageConfig.Codec
|
||||
e.Storage.Storage, e.DestroyFunc = opts.Decorator(
|
||||
opts.StorageConfig,
|
||||
e.NewFunc(),
|
||||
prefix,
|
||||
keyFunc,
|
||||
e.NewFunc,
|
||||
e.NewListFunc,
|
||||
attrFunc,
|
||||
triggerFunc,
|
||||
|
|
|
|||
4
vendor/k8s.io/apiserver/pkg/registry/generic/storage_decorator.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/registry/generic/storage_decorator.go
generated
vendored
|
|
@ -28,9 +28,9 @@ import (
|
|||
// and an associated DestroyFunc from given parameters.
|
||||
type StorageDecorator func(
|
||||
config *storagebackend.Config,
|
||||
objectType runtime.Object,
|
||||
resourcePrefix string,
|
||||
keyFunc func(obj runtime.Object) (string, error),
|
||||
newFunc func() runtime.Object,
|
||||
newListFunc func() runtime.Object,
|
||||
getAttrsFunc storage.AttrFunc,
|
||||
trigger storage.TriggerPublisherFunc) (storage.Interface, factory.DestroyFunc)
|
||||
|
|
@ -39,9 +39,9 @@ type StorageDecorator func(
|
|||
// without any decoration.
|
||||
func UndecoratedStorage(
|
||||
config *storagebackend.Config,
|
||||
objectType runtime.Object,
|
||||
resourcePrefix string,
|
||||
keyFunc func(obj runtime.Object) (string, error),
|
||||
newFunc func() runtime.Object,
|
||||
newListFunc func() runtime.Object,
|
||||
getAttrsFunc storage.AttrFunc,
|
||||
trigger storage.TriggerPublisherFunc) (storage.Interface, factory.DestroyFunc) {
|
||||
|
|
|
|||
1
vendor/k8s.io/apiserver/pkg/registry/rest/create.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/registry/rest/create.go
generated
vendored
|
|
@ -175,6 +175,7 @@ func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes a
|
|||
staticAttributes.GetResource(),
|
||||
staticAttributes.GetSubresource(),
|
||||
staticAttributes.GetOperation(),
|
||||
staticAttributes.GetOperationOptions(),
|
||||
staticAttributes.IsDryRun(),
|
||||
staticAttributes.GetUserInfo(),
|
||||
)
|
||||
|
|
|
|||
41
vendor/k8s.io/apiserver/pkg/registry/rest/delete.go
generated
vendored
41
vendor/k8s.io/apiserver/pkg/registry/rest/delete.go
generated
vendored
|
|
@ -26,6 +26,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// RESTDeleteStrategy defines deletion behavior on an object that follows Kubernetes
|
||||
|
|
@ -140,3 +141,43 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime.
|
|||
}
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
// AdmissionToValidateObjectDeleteFunc returns a admission validate func for object deletion
|
||||
func AdmissionToValidateObjectDeleteFunc(admit admission.Interface, staticAttributes admission.Attributes, objInterfaces admission.ObjectInterfaces) ValidateObjectFunc {
|
||||
mutatingAdmission, isMutatingAdmission := admit.(admission.MutationInterface)
|
||||
validatingAdmission, isValidatingAdmission := admit.(admission.ValidationInterface)
|
||||
|
||||
mutating := isMutatingAdmission && mutatingAdmission.Handles(staticAttributes.GetOperation())
|
||||
validating := isValidatingAdmission && validatingAdmission.Handles(staticAttributes.GetOperation())
|
||||
|
||||
return func(old runtime.Object) error {
|
||||
if !mutating && !validating {
|
||||
return nil
|
||||
}
|
||||
finalAttributes := admission.NewAttributesRecord(
|
||||
nil,
|
||||
// Deep copy the object to avoid accidentally changing the object.
|
||||
old.DeepCopyObject(),
|
||||
staticAttributes.GetKind(),
|
||||
staticAttributes.GetNamespace(),
|
||||
staticAttributes.GetName(),
|
||||
staticAttributes.GetResource(),
|
||||
staticAttributes.GetSubresource(),
|
||||
staticAttributes.GetOperation(),
|
||||
staticAttributes.GetOperationOptions(),
|
||||
staticAttributes.IsDryRun(),
|
||||
staticAttributes.GetUserInfo(),
|
||||
)
|
||||
if mutating {
|
||||
if err := mutatingAdmission.Admit(finalAttributes, objInterfaces); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if validating {
|
||||
if err := validatingAdmission.Validate(finalAttributes, objInterfaces); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
vendor/k8s.io/apiserver/pkg/registry/rest/rest.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/registry/rest/rest.go
generated
vendored
|
|
@ -148,6 +148,7 @@ type TableConvertor interface {
|
|||
// RESTful object.
|
||||
type GracefulDeleter interface {
|
||||
// Delete finds a resource in the storage and deletes it.
|
||||
// The delete attempt is validated by the deleteValidation first.
|
||||
// If options are provided, the resource will attempt to honor them or return an invalid
|
||||
// request error.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
|
|
@ -156,18 +157,19 @@ type GracefulDeleter interface {
|
|||
// information about deletion.
|
||||
// It also returns a boolean which is set to true if the resource was instantly
|
||||
// deleted or false if it will be deleted asynchronously.
|
||||
Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error)
|
||||
Delete(ctx context.Context, name string, deleteValidation ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CollectionDeleter is an object that can delete a collection
|
||||
// of RESTful resources.
|
||||
type CollectionDeleter interface {
|
||||
// DeleteCollection selects all resources in the storage matching given 'listOptions'
|
||||
// and deletes them. If 'options' are provided, the resource will attempt to honor
|
||||
// them or return an invalid request error.
|
||||
// and deletes them. The delete attempt is validated by the deleteValidation first.
|
||||
// If 'options' are provided, the resource will attempt to honor them or return an
|
||||
// invalid request error.
|
||||
// DeleteCollection may not be atomic - i.e. it may delete some objects and still
|
||||
// return an error after it. On success, returns a list of deleted objects.
|
||||
DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error)
|
||||
DeleteCollection(ctx context.Context, deleteValidation ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Creater is an object that can create an instance of a RESTful object.
|
||||
|
|
|
|||
9
vendor/k8s.io/apiserver/pkg/registry/rest/table.go
generated
vendored
9
vendor/k8s.io/apiserver/pkg/registry/rest/table.go
generated
vendored
|
|
@ -67,15 +67,18 @@ func (c defaultTableConvertor) ConvertToTable(ctx context.Context, object runtim
|
|||
table.ResourceVersion = m.GetResourceVersion()
|
||||
table.SelfLink = m.GetSelfLink()
|
||||
table.Continue = m.GetContinue()
|
||||
table.RemainingItemCount = m.GetRemainingItemCount()
|
||||
} else {
|
||||
if m, err := meta.CommonAccessor(object); err == nil {
|
||||
table.ResourceVersion = m.GetResourceVersion()
|
||||
table.SelfLink = m.GetSelfLink()
|
||||
}
|
||||
}
|
||||
table.ColumnDefinitions = []metav1beta1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
|
||||
{Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]},
|
||||
if opt, ok := tableOptions.(*metav1beta1.TableOptions); !ok || !opt.NoHeaders {
|
||||
table.ColumnDefinitions = []metav1beta1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
|
||||
{Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]},
|
||||
}
|
||||
}
|
||||
return &table, nil
|
||||
}
|
||||
|
|
|
|||
1
vendor/k8s.io/apiserver/pkg/registry/rest/update.go
generated
vendored
1
vendor/k8s.io/apiserver/pkg/registry/rest/update.go
generated
vendored
|
|
@ -271,6 +271,7 @@ func AdmissionToValidateObjectUpdateFunc(admit admission.Interface, staticAttrib
|
|||
staticAttributes.GetResource(),
|
||||
staticAttributes.GetSubresource(),
|
||||
staticAttributes.GetOperation(),
|
||||
staticAttributes.GetOperationOptions(),
|
||||
staticAttributes.IsDryRun(),
|
||||
staticAttributes.GetUserInfo(),
|
||||
)
|
||||
|
|
|
|||
75
vendor/k8s.io/apiserver/pkg/server/config.go
generated
vendored
75
vendor/k8s.io/apiserver/pkg/server/config.go
generated
vendored
|
|
@ -35,6 +35,7 @@ import (
|
|||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup"
|
||||
|
|
@ -159,7 +160,7 @@ type Config struct {
|
|||
// patch may cause.
|
||||
// This affects all places that applies json patch in the binary.
|
||||
JSONPatchMaxCopyBytes int64
|
||||
// The limit on the request body size that would be accepted and decoded in a write request.
|
||||
// The limit on the request size that would be accepted and decoded in a write request
|
||||
// 0 means no limit.
|
||||
MaxRequestBodyBytes int64
|
||||
// MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further
|
||||
|
|
@ -188,6 +189,10 @@ type Config struct {
|
|||
// kube-proxy, services, etc.) can reach the GenericAPIServer.
|
||||
// If nil or 0.0.0.0, the host's default interface will be used.
|
||||
PublicAddress net.IP
|
||||
|
||||
// EquivalentResourceRegistry provides information about resources equivalent to a given resource,
|
||||
// and the kind associated with a given resource. As resources are installed, they are registered here.
|
||||
EquivalentResourceRegistry runtime.EquivalentResourceRegistry
|
||||
}
|
||||
|
||||
type RecommendedConfig struct {
|
||||
|
|
@ -266,22 +271,20 @@ func NewConfig(codecs serializer.CodecFactory) *Config {
|
|||
MaxMutatingRequestsInFlight: 200,
|
||||
RequestTimeout: time.Duration(60) * time.Second,
|
||||
MinRequestTimeout: 1800,
|
||||
// 10MB is the recommended maximum client request size in bytes
|
||||
// 1.5MB is the recommended client request size in byte
|
||||
// the etcd server should accept. See
|
||||
// https://github.com/etcd-io/etcd/blob/release-3.3/etcdserver/server.go#L90.
|
||||
// https://github.com/etcd-io/etcd/blob/release-3.4/embed/config.go#L56.
|
||||
// A request body might be encoded in json, and is converted to
|
||||
// proto when persisted in etcd. Assuming the upper bound of
|
||||
// the size ratio is 10:1, we set 100MB as the largest size
|
||||
// proto when persisted in etcd, so we allow 2x as the largest size
|
||||
// increase the "copy" operations in a json patch may cause.
|
||||
JSONPatchMaxCopyBytes: int64(100 * 1024 * 1024),
|
||||
// 10MB is the recommended maximum client request size in bytes
|
||||
JSONPatchMaxCopyBytes: int64(3 * 1024 * 1024),
|
||||
// 1.5MB is the recommended client request size in byte
|
||||
// the etcd server should accept. See
|
||||
// https://github.com/etcd-io/etcd/blob/release-3.3/etcdserver/server.go#L90.
|
||||
// https://github.com/etcd-io/etcd/blob/release-3.4/embed/config.go#L56.
|
||||
// A request body might be encoded in json, and is converted to
|
||||
// proto when persisted in etcd. Assuming the upper bound of
|
||||
// the size ratio is 10:1, we set 100MB as the largest request
|
||||
// proto when persisted in etcd, so we allow 2x as the largest request
|
||||
// body size to be accepted and decoded in a write request.
|
||||
MaxRequestBodyBytes: int64(100 * 1024 * 1024),
|
||||
MaxRequestBodyBytes: int64(3 * 1024 * 1024),
|
||||
EnableAPIResponseCompression: utilfeature.DefaultFeatureGate.Enabled(features.APIResponseCompression),
|
||||
|
||||
// Default to treating watch as a long-running operation
|
||||
|
|
@ -417,6 +420,21 @@ func (c *Config) Complete(informers informers.SharedInformerFactory) CompletedCo
|
|||
c.RequestInfoResolver = NewRequestInfoResolver(c)
|
||||
}
|
||||
|
||||
if c.EquivalentResourceRegistry == nil {
|
||||
if c.RESTOptionsGetter == nil {
|
||||
c.EquivalentResourceRegistry = runtime.NewEquivalentResourceRegistry()
|
||||
} else {
|
||||
c.EquivalentResourceRegistry = runtime.NewEquivalentResourceRegistryWithIdentity(func(groupResource schema.GroupResource) string {
|
||||
// use the storage prefix as the key if possible
|
||||
if opts, err := c.RESTOptionsGetter.GetRESTOptions(groupResource); err == nil {
|
||||
return opts.ResourcePrefix
|
||||
}
|
||||
// otherwise return "" to use the default key (parent GV name)
|
||||
return ""
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return CompletedConfig{&completedConfig{c, informers}}
|
||||
}
|
||||
|
||||
|
|
@ -436,6 +454,9 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
|
|||
if c.LoopbackClientConfig == nil {
|
||||
return nil, fmt.Errorf("Genericapiserver.New() called with config.LoopbackClientConfig == nil")
|
||||
}
|
||||
if c.EquivalentResourceRegistry == nil {
|
||||
return nil, fmt.Errorf("Genericapiserver.New() called with config.EquivalentResourceRegistry == nil")
|
||||
}
|
||||
|
||||
handlerChainBuilder := func(handler http.Handler) http.Handler {
|
||||
return c.BuildHandlerChainFunc(handler, c.Config)
|
||||
|
|
@ -443,15 +464,16 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
|
|||
apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler())
|
||||
|
||||
s := &GenericAPIServer{
|
||||
discoveryAddresses: c.DiscoveryAddresses,
|
||||
LoopbackClientConfig: c.LoopbackClientConfig,
|
||||
legacyAPIGroupPrefixes: c.LegacyAPIGroupPrefixes,
|
||||
admissionControl: c.AdmissionControl,
|
||||
Serializer: c.Serializer,
|
||||
AuditBackend: c.AuditBackend,
|
||||
Authorizer: c.Authorization.Authorizer,
|
||||
delegationTarget: delegationTarget,
|
||||
HandlerChainWaitGroup: c.HandlerChainWaitGroup,
|
||||
discoveryAddresses: c.DiscoveryAddresses,
|
||||
LoopbackClientConfig: c.LoopbackClientConfig,
|
||||
legacyAPIGroupPrefixes: c.LegacyAPIGroupPrefixes,
|
||||
admissionControl: c.AdmissionControl,
|
||||
Serializer: c.Serializer,
|
||||
AuditBackend: c.AuditBackend,
|
||||
Authorizer: c.Authorization.Authorizer,
|
||||
delegationTarget: delegationTarget,
|
||||
EquivalentResourceRegistry: c.EquivalentResourceRegistry,
|
||||
HandlerChainWaitGroup: c.HandlerChainWaitGroup,
|
||||
|
||||
minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second,
|
||||
ShutdownTimeout: c.RequestTimeout,
|
||||
|
|
@ -614,9 +636,18 @@ func (s *SecureServingInfo) HostPort() (string, int, error) {
|
|||
}
|
||||
|
||||
// AuthorizeClientBearerToken wraps the authenticator and authorizer in loopback authentication logic
|
||||
// if the loopback client config is specified AND it has a bearer token.
|
||||
// if the loopback client config is specified AND it has a bearer token. Note that if either authn or
|
||||
// authz is nil, this function won't add a token authenticator or authorizer.
|
||||
func AuthorizeClientBearerToken(loopback *restclient.Config, authn *AuthenticationInfo, authz *AuthorizationInfo) {
|
||||
if loopback == nil || authn == nil || authz == nil || authn.Authenticator == nil && authz.Authorizer == nil || len(loopback.BearerToken) == 0 {
|
||||
if loopback == nil || len(loopback.BearerToken) == 0 {
|
||||
return
|
||||
}
|
||||
if authn == nil || authz == nil {
|
||||
// prevent nil pointer panic
|
||||
}
|
||||
if authn.Authenticator == nil || authz.Authorizer == nil {
|
||||
// authenticator or authorizer might be nil if we want to bypass authz/authn
|
||||
// and we also do nothing in this case.
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
9
vendor/k8s.io/apiserver/pkg/server/config_selfclient.go
generated
vendored
9
vendor/k8s.io/apiserver/pkg/server/config_selfclient.go
generated
vendored
|
|
@ -38,12 +38,9 @@ func (s *SecureServingInfo) NewClientConfig(caCert []byte) (*restclient.Config,
|
|||
}
|
||||
|
||||
return &restclient.Config{
|
||||
// Increase QPS limits. The client is currently passed to all admission plugins,
|
||||
// and those can be throttled in case of higher load on apiserver - see #22340 and #22422
|
||||
// for more details. Once #22422 is fixed, we may want to remove it.
|
||||
QPS: 50,
|
||||
Burst: 100,
|
||||
Host: "https://" + net.JoinHostPort(host, port),
|
||||
// Do not limit loopback client QPS.
|
||||
QPS: -1,
|
||||
Host: "https://" + net.JoinHostPort(host, port),
|
||||
// override the ServerName to select our loopback certificate via SNI. This name is also
|
||||
// used by the client to compare the returns server certificate against.
|
||||
TLSClientConfig: restclient.TLSClientConfig{
|
||||
|
|
|
|||
2
vendor/k8s.io/apiserver/pkg/server/deprecated_insecure_serving.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/server/deprecated_insecure_serving.go
generated
vendored
|
|
@ -29,6 +29,8 @@ import (
|
|||
)
|
||||
|
||||
// DeprecatedInsecureServingInfo is the main context object for the insecure http server.
|
||||
// HTTP does NOT include authentication or authorization.
|
||||
// You shouldn't be using this. It makes sig-auth sad.
|
||||
type DeprecatedInsecureServingInfo struct {
|
||||
// Listener is the secure server network listener.
|
||||
Listener net.Listener
|
||||
|
|
|
|||
28
vendor/k8s.io/apiserver/pkg/server/filters/timeout.go
generated
vendored
28
vendor/k8s.io/apiserver/pkg/server/filters/timeout.go
generated
vendored
|
|
@ -28,6 +28,7 @@ import (
|
|||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
|
@ -92,28 +93,49 @@ func (t *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
errCh := make(chan interface{})
|
||||
// resultCh is used as both errCh and stopCh
|
||||
resultCh := make(chan interface{})
|
||||
tw := newTimeoutWriter(w)
|
||||
go func() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
// Same as stdlib http server code. Manually allocate stack
|
||||
// trace buffer size to prevent excessively large logs
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
err = fmt.Sprintf("%v\n%s", err, buf)
|
||||
}
|
||||
errCh <- err
|
||||
resultCh <- err
|
||||
}()
|
||||
t.handler.ServeHTTP(tw, r)
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
case err := <-resultCh:
|
||||
// panic if error occurs; stop otherwise
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
case <-after:
|
||||
defer func() {
|
||||
// resultCh needs to have a reader, since the function doing
|
||||
// the work needs to send to it. This is defer'd to ensure it runs
|
||||
// ever if the post timeout work itself panics.
|
||||
go func() {
|
||||
res := <-resultCh
|
||||
if res != nil {
|
||||
switch t := res.(type) {
|
||||
case error:
|
||||
utilruntime.HandleError(t)
|
||||
default:
|
||||
utilruntime.HandleError(fmt.Errorf("%v", res))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
postTimeoutFn()
|
||||
tw.timeout(err)
|
||||
}
|
||||
|
|
|
|||
11
vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
generated
vendored
11
vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
generated
vendored
|
|
@ -157,6 +157,10 @@ type GenericAPIServer struct {
|
|||
// the create-on-update case
|
||||
Authorizer authorizer.Authorizer
|
||||
|
||||
// EquivalentResourceRegistry provides information about resources equivalent to a given resource,
|
||||
// and the kind associated with a given resource. As resources are installed, they are registered here.
|
||||
EquivalentResourceRegistry runtime.EquivalentResourceRegistry
|
||||
|
||||
// enableAPIResponseCompression indicates whether API Responses should support compression
|
||||
// if the client requests it via Accept-Encoding
|
||||
enableAPIResponseCompression bool
|
||||
|
|
@ -258,10 +262,13 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
|
|||
|
||||
// Register audit backend preShutdownHook.
|
||||
if s.AuditBackend != nil {
|
||||
s.AddPreShutdownHook("audit-backend", func() error {
|
||||
err := s.AddPreShutdownHook("audit-backend", func() error {
|
||||
s.AuditBackend.Shutdown()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to add pre-shutdown hook for audit-backend %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return preparedGenericAPIServer{s}
|
||||
|
|
@ -464,6 +471,8 @@ func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupV
|
|||
Typer: apiGroupInfo.Scheme,
|
||||
Linker: runtime.SelfLinker(meta.NewAccessor()),
|
||||
|
||||
EquivalentResourceRegistry: s.EquivalentResourceRegistry,
|
||||
|
||||
Admit: s.admissionControl,
|
||||
MinRequestTimeout: s.minRequestTimeout,
|
||||
EnableAPIResponseCompression: s.enableAPIResponseCompression,
|
||||
|
|
|
|||
2
vendor/k8s.io/apiserver/pkg/server/healthz/doc.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/server/healthz/doc.go
generated
vendored
|
|
@ -17,5 +17,5 @@ limitations under the License.
|
|||
// Package healthz implements basic http server health checking.
|
||||
// Usage:
|
||||
// import "k8s.io/apiserver/pkg/server/healthz"
|
||||
// healthz.DefaultHealthz()
|
||||
// healthz.InstallHandler(mux)
|
||||
package healthz // import "k8s.io/apiserver/pkg/server/healthz"
|
||||
|
|
|
|||
10
vendor/k8s.io/apiserver/pkg/server/healthz/healthz.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/server/healthz/healthz.go
generated
vendored
|
|
@ -37,15 +37,6 @@ type HealthzChecker interface {
|
|||
Check(req *http.Request) error
|
||||
}
|
||||
|
||||
var defaultHealthz = sync.Once{}
|
||||
|
||||
// DefaultHealthz installs the default healthz check to the http.DefaultServeMux.
|
||||
func DefaultHealthz(checks ...HealthzChecker) {
|
||||
defaultHealthz.Do(func() {
|
||||
InstallHandler(http.DefaultServeMux, checks...)
|
||||
})
|
||||
}
|
||||
|
||||
// PingHealthz returns true automatically when checked
|
||||
var PingHealthz HealthzChecker = ping{}
|
||||
|
||||
|
|
@ -181,6 +172,7 @@ func handleRootHealthz(checks ...HealthzChecker) http.HandlerFunc {
|
|||
}
|
||||
// always be verbose on failure
|
||||
if failed {
|
||||
klog.V(2).Infof("%vhealthz check failed", verboseOut.String())
|
||||
http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
12
vendor/k8s.io/apiserver/pkg/server/httplog/httplog.go
generated
vendored
12
vendor/k8s.io/apiserver/pkg/server/httplog/httplog.go
generated
vendored
|
|
@ -125,13 +125,13 @@ func (rl *respLogger) StacktraceWhen(pred StacktracePred) *respLogger {
|
|||
// StatusIsNot returns a StacktracePred which will cause stacktraces to be logged
|
||||
// for any status *not* in the given list.
|
||||
func StatusIsNot(statuses ...int) StacktracePred {
|
||||
statusesNoTrace := map[int]bool{}
|
||||
for _, s := range statuses {
|
||||
statusesNoTrace[s] = true
|
||||
}
|
||||
return func(status int) bool {
|
||||
for _, s := range statuses {
|
||||
if status == s {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
_, ok := statusesNoTrace[status]
|
||||
return !ok
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
3
vendor/k8s.io/apiserver/pkg/server/options/etcd.go
generated
vendored
3
vendor/k8s.io/apiserver/pkg/server/options/etcd.go
generated
vendored
|
|
@ -229,6 +229,7 @@ func (f *SimpleRestOptionsFactory) GetRESTOptions(resource schema.GroupResource)
|
|||
if !ok {
|
||||
cacheSize = f.Options.DefaultWatchCacheSize
|
||||
}
|
||||
// depending on cache size this might return an undecorated storage
|
||||
ret.Decorator = genericregistry.StorageWithCacher(cacheSize)
|
||||
}
|
||||
return ret, nil
|
||||
|
|
@ -262,6 +263,7 @@ func (f *StorageFactoryRestOptionsFactory) GetRESTOptions(resource schema.GroupR
|
|||
if !ok {
|
||||
cacheSize = f.Options.DefaultWatchCacheSize
|
||||
}
|
||||
// depending on cache size this might return an undecorated storage
|
||||
ret.Decorator = genericregistry.StorageWithCacher(cacheSize)
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +287,6 @@ func ParseWatchCacheSizes(cacheSizes []string) (map[schema.GroupResource]int, er
|
|||
if size < 0 {
|
||||
return nil, fmt.Errorf("watch cache size cannot be negative: %s", c)
|
||||
}
|
||||
|
||||
watchCacheSizes[schema.ParseGroupResource(tokens[0])] = size
|
||||
}
|
||||
return watchCacheSizes, nil
|
||||
|
|
|
|||
36
vendor/k8s.io/apiserver/pkg/server/options/server_run_options.go
generated
vendored
36
vendor/k8s.io/apiserver/pkg/server/options/server_run_options.go
generated
vendored
|
|
@ -27,7 +27,7 @@ import (
|
|||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
|
||||
// add the generic feature gates
|
||||
_ "k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
|
@ -49,8 +49,9 @@ type ServerRunOptions struct {
|
|||
// decoded in a write request. 0 means no limit.
|
||||
// We intentionally did not add a flag for this option. Users of the
|
||||
// apiserver library can wire it to a flag.
|
||||
MaxRequestBodyBytes int64
|
||||
TargetRAMMB int
|
||||
MaxRequestBodyBytes int64
|
||||
TargetRAMMB int
|
||||
EnableInfightQuotaHandler bool
|
||||
}
|
||||
|
||||
func NewServerRunOptions() *ServerRunOptions {
|
||||
|
|
@ -104,11 +105,27 @@ func (s *ServerRunOptions) Validate() []error {
|
|||
if s.TargetRAMMB < 0 {
|
||||
errors = append(errors, fmt.Errorf("--target-ram-mb can not be negative value"))
|
||||
}
|
||||
if s.MaxRequestsInFlight < 0 {
|
||||
errors = append(errors, fmt.Errorf("--max-requests-inflight can not be negative value"))
|
||||
}
|
||||
if s.MaxMutatingRequestsInFlight < 0 {
|
||||
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight can not be negative value"))
|
||||
|
||||
if s.EnableInfightQuotaHandler {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.RequestManagement) {
|
||||
errors = append(errors, fmt.Errorf("--enable-inflight-quota-handler can not be set if feature "+
|
||||
"gate RequestManagement is disabled"))
|
||||
}
|
||||
if s.MaxMutatingRequestsInFlight != 0 {
|
||||
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight=%v "+
|
||||
"can not be set if enabled inflight quota handler", s.MaxMutatingRequestsInFlight))
|
||||
}
|
||||
if s.MaxRequestsInFlight != 0 {
|
||||
errors = append(errors, fmt.Errorf("--max-requests-inflight=%v "+
|
||||
"can not be set if enabled inflight quota handler", s.MaxRequestsInFlight))
|
||||
}
|
||||
} else {
|
||||
if s.MaxRequestsInFlight < 0 {
|
||||
errors = append(errors, fmt.Errorf("--max-requests-inflight can not be negative value"))
|
||||
}
|
||||
if s.MaxMutatingRequestsInFlight < 0 {
|
||||
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight can not be negative value"))
|
||||
}
|
||||
}
|
||||
|
||||
if s.RequestTimeout.Nanoseconds() < 0 {
|
||||
|
|
@ -174,5 +191,8 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
|
|||
"handler, which picks a randomized value above this number as the connection timeout, "+
|
||||
"to spread out load.")
|
||||
|
||||
fs.BoolVar(&s.EnableInfightQuotaHandler, "enable-inflight-quota-handler", s.EnableInfightQuotaHandler, ""+
|
||||
"If true, replace the max-in-flight handler with an enhanced one that queues and dispatches with priority and fairness")
|
||||
|
||||
utilfeature.DefaultMutableFeatureGate.AddFlag(fs)
|
||||
}
|
||||
|
|
|
|||
383
vendor/k8s.io/apiserver/pkg/storage/cacher/cacher.go
generated
vendored
383
vendor/k8s.io/apiserver/pkg/storage/cacher/cacher.go
generated
vendored
|
|
@ -33,6 +33,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
|
@ -53,6 +54,13 @@ var (
|
|||
},
|
||||
[]string{"resource"},
|
||||
)
|
||||
emptyFunc = func() {}
|
||||
)
|
||||
|
||||
const (
|
||||
// storageWatchListPageSize is the cacher's request chunk size of
|
||||
// initial and resync watch lists to storage.
|
||||
storageWatchListPageSize = int64(10000)
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -72,7 +80,6 @@ type Config struct {
|
|||
|
||||
// The Cache will be caching objects of a given Type and assumes that they
|
||||
// are all stored under ResourcePrefix directory in the underlying database.
|
||||
Type interface{}
|
||||
ResourcePrefix string
|
||||
|
||||
// KeyFunc is used to get a key in the underlying storage for a given object.
|
||||
|
|
@ -85,6 +92,9 @@ type Config struct {
|
|||
// needs to process an incoming event.
|
||||
TriggerPublisherFunc storage.TriggerPublisherFunc
|
||||
|
||||
// NewFunc is a function that creates new empty object storing a object of type Type.
|
||||
NewFunc func() runtime.Object
|
||||
|
||||
// NewList is a function that creates new empty object storing a list of
|
||||
// objects of type Type.
|
||||
NewListFunc func() runtime.Object
|
||||
|
|
@ -150,6 +160,53 @@ func (i *indexedWatchers) terminateAll(objectType reflect.Type, done func(*cache
|
|||
}
|
||||
}
|
||||
|
||||
// As we don't need a high precision here, we keep all watchers timeout within a
|
||||
// second in a bucket, and pop up them once at the timeout. To be more specific,
|
||||
// if you set fire time at X, you can get the bookmark within (X-1,X+1) period.
|
||||
// This is NOT thread-safe.
|
||||
type watcherBookmarkTimeBuckets struct {
|
||||
watchersBuckets map[int64][]*cacheWatcher
|
||||
startBucketID int64
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
func newTimeBucketWatchers(clock clock.Clock) *watcherBookmarkTimeBuckets {
|
||||
return &watcherBookmarkTimeBuckets{
|
||||
watchersBuckets: make(map[int64][]*cacheWatcher),
|
||||
startBucketID: clock.Now().Unix(),
|
||||
clock: clock,
|
||||
}
|
||||
}
|
||||
|
||||
// adds a watcher to the bucket, if the deadline is before the start, it will be
|
||||
// added to the first one.
|
||||
func (t *watcherBookmarkTimeBuckets) addWatcher(w *cacheWatcher) bool {
|
||||
nextTime, ok := w.nextBookmarkTime(t.clock.Now())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
bucketID := nextTime.Unix()
|
||||
if bucketID < t.startBucketID {
|
||||
bucketID = t.startBucketID
|
||||
}
|
||||
watchers, _ := t.watchersBuckets[bucketID]
|
||||
t.watchersBuckets[bucketID] = append(watchers, w)
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *watcherBookmarkTimeBuckets) popExpiredWatchers() [][]*cacheWatcher {
|
||||
currentBucketID := t.clock.Now().Unix()
|
||||
// There should be one or two elements in almost all cases
|
||||
expiredWatchers := make([][]*cacheWatcher, 0, 2)
|
||||
for ; t.startBucketID <= currentBucketID; t.startBucketID++ {
|
||||
if watchers, ok := t.watchersBuckets[t.startBucketID]; ok {
|
||||
delete(t.watchersBuckets, t.startBucketID)
|
||||
expiredWatchers = append(expiredWatchers, watchers)
|
||||
}
|
||||
}
|
||||
return expiredWatchers
|
||||
}
|
||||
|
||||
type filterWithAttrsFunc func(key string, l labels.Set, f fields.Set) bool
|
||||
|
||||
// Cacher is responsible for serving WATCH and LIST requests for a given
|
||||
|
|
@ -188,6 +245,9 @@ type Cacher struct {
|
|||
// Versioner is used to handle resource versions.
|
||||
versioner storage.Versioner
|
||||
|
||||
// newFunc is a function that creates new empty object storing a object of type Type.
|
||||
newFunc func() runtime.Object
|
||||
|
||||
// triggerFunc is used for optimizing amount of watchers that needs to process
|
||||
// an incoming event.
|
||||
triggerFunc storage.TriggerPublisherFunc
|
||||
|
|
@ -206,6 +266,7 @@ type Cacher struct {
|
|||
stopCh chan struct{}
|
||||
stopWg sync.WaitGroup
|
||||
|
||||
clock clock.Clock
|
||||
// timer is used to avoid unnecessary allocations in underlying watchers.
|
||||
timer *time.Timer
|
||||
|
||||
|
|
@ -219,32 +280,31 @@ type Cacher struct {
|
|||
// during current dispatching, but stopping was deferred to the end of
|
||||
// dispatching that event to avoid race with closing channels in watchers.
|
||||
watchersToStop []*cacheWatcher
|
||||
// Maintain a timeout queue to send the bookmark event before the watcher times out.
|
||||
bookmarkWatchers *watcherBookmarkTimeBuckets
|
||||
// watchBookmark feature-gate
|
||||
watchBookmarkEnabled bool
|
||||
}
|
||||
|
||||
// NewCacherFromConfig creates a new Cacher responsible for servicing WATCH and LIST requests from
|
||||
// its internal cache and updating its cache in the background based on the
|
||||
// given configuration.
|
||||
func NewCacherFromConfig(config Config) *Cacher {
|
||||
watchCache := newWatchCache(config.CacheCapacity, config.KeyFunc, config.GetAttrsFunc, config.Versioner)
|
||||
listerWatcher := newCacherListerWatcher(config.Storage, config.ResourcePrefix, config.NewListFunc)
|
||||
reflectorName := "storage/cacher.go:" + config.ResourcePrefix
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
obj := config.NewFunc()
|
||||
// Give this error when it is constructed rather than when you get the
|
||||
// first watch item, because it's much easier to track down that way.
|
||||
if obj, ok := config.Type.(runtime.Object); ok {
|
||||
if err := runtime.CheckCodec(config.Codec, obj); err != nil {
|
||||
panic("storage codec doesn't seem to match given type: " + err.Error())
|
||||
}
|
||||
if err := runtime.CheckCodec(config.Codec, obj); err != nil {
|
||||
panic("storage codec doesn't seem to match given type: " + err.Error())
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
clock := clock.RealClock{}
|
||||
cacher := &Cacher{
|
||||
ready: newReady(),
|
||||
storage: config.Storage,
|
||||
objectType: reflect.TypeOf(config.Type),
|
||||
watchCache: watchCache,
|
||||
reflector: cache.NewNamedReflector(reflectorName, listerWatcher, config.Type, watchCache, 0),
|
||||
objectType: reflect.TypeOf(obj),
|
||||
versioner: config.Versioner,
|
||||
newFunc: config.NewFunc,
|
||||
triggerFunc: config.TriggerPublisherFunc,
|
||||
watcherIdx: 0,
|
||||
watchers: indexedWatchers{
|
||||
|
|
@ -259,15 +319,39 @@ func NewCacherFromConfig(config Config) *Cacher {
|
|||
// - reflector.ListAndWatch
|
||||
// and there are no guarantees on the order that they will stop.
|
||||
// So we will be simply closing the channel, and synchronizing on the WaitGroup.
|
||||
stopCh: stopCh,
|
||||
timer: time.NewTimer(time.Duration(0)),
|
||||
stopCh: stopCh,
|
||||
clock: clock,
|
||||
timer: time.NewTimer(time.Duration(0)),
|
||||
bookmarkWatchers: newTimeBucketWatchers(clock),
|
||||
watchBookmarkEnabled: utilfeature.DefaultFeatureGate.Enabled(features.WatchBookmark),
|
||||
}
|
||||
watchCache.SetOnEvent(cacher.processEvent)
|
||||
|
||||
// Ensure that timer is stopped.
|
||||
if !cacher.timer.Stop() {
|
||||
// Consume triggered (but not yet received) timer event
|
||||
// so that future reuse does not get a spurious timeout.
|
||||
<-cacher.timer.C
|
||||
}
|
||||
|
||||
watchCache := newWatchCache(
|
||||
config.CacheCapacity, config.KeyFunc, cacher.processEvent, config.GetAttrsFunc, config.Versioner)
|
||||
listerWatcher := NewCacherListerWatcher(config.Storage, config.ResourcePrefix, config.NewListFunc)
|
||||
reflectorName := "storage/cacher.go:" + config.ResourcePrefix
|
||||
|
||||
reflector := cache.NewNamedReflector(reflectorName, listerWatcher, obj, watchCache, 0)
|
||||
// Configure reflector's pager to for an appropriate pagination chunk size for fetching data from
|
||||
// storage. The pager falls back to full list if paginated list calls fail due to an "Expired" error.
|
||||
reflector.WatchListPageSize = storageWatchListPageSize
|
||||
|
||||
cacher.watchCache = watchCache
|
||||
cacher.reflector = reflector
|
||||
|
||||
go cacher.dispatchEvents()
|
||||
|
||||
cacher.stopWg.Add(1)
|
||||
go func() {
|
||||
defer cacher.stopWg.Done()
|
||||
defer cacher.terminateAllWatchers()
|
||||
wait.Until(
|
||||
func() {
|
||||
if !cacher.isStopped() {
|
||||
|
|
@ -277,13 +361,6 @@ func NewCacherFromConfig(config Config) *Cacher {
|
|||
)
|
||||
}()
|
||||
|
||||
// Ensure that timer is stopped.
|
||||
if !cacher.timer.Stop() {
|
||||
// Consume triggered (but not yet received) timer event
|
||||
// so that future reuse does not get a spurious timeout.
|
||||
<-cacher.timer.C
|
||||
}
|
||||
|
||||
return cacher
|
||||
}
|
||||
|
||||
|
|
@ -326,8 +403,8 @@ func (c *Cacher) Create(ctx context.Context, key string, obj, out runtime.Object
|
|||
}
|
||||
|
||||
// Delete implements storage.Interface.
|
||||
func (c *Cacher) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions) error {
|
||||
return c.storage.Delete(ctx, key, out, preconditions)
|
||||
func (c *Cacher) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error {
|
||||
return c.storage.Delete(ctx, key, out, preconditions, validateDeletion)
|
||||
}
|
||||
|
||||
// Watch implements storage.Interface.
|
||||
|
|
@ -339,21 +416,6 @@ func (c *Cacher) Watch(ctx context.Context, key string, resourceVersion string,
|
|||
|
||||
c.ready.wait()
|
||||
|
||||
// We explicitly use thread unsafe version and do locking ourself to ensure that
|
||||
// no new events will be processed in the meantime. The watchCache will be unlocked
|
||||
// on return from this function.
|
||||
// Note that we cannot do it under Cacher lock, to avoid a deadlock, since the
|
||||
// underlying watchCache is calling processEvent under its lock.
|
||||
c.watchCache.RLock()
|
||||
defer c.watchCache.RUnlock()
|
||||
initEvents, err := c.watchCache.GetAllEventsSinceThreadUnsafe(watchRV)
|
||||
if err != nil {
|
||||
// To match the uncached watch implementation, once we have passed authn/authz/admission,
|
||||
// and successfully parsed a resource version, other errors must fail with a watch event of type ERROR,
|
||||
// rather than a directly returned error.
|
||||
return newErrWatcher(err), nil
|
||||
}
|
||||
|
||||
triggerValue, triggerSupported := "", false
|
||||
// TODO: Currently we assume that in a given Cacher object, any <predicate> that is
|
||||
// passed here is aware of exactly the same trigger (at most one).
|
||||
|
|
@ -377,6 +439,29 @@ func (c *Cacher) Watch(ctx context.Context, key string, resourceVersion string,
|
|||
chanSize = 1000
|
||||
}
|
||||
|
||||
// Determine watch timeout('0' means deadline is not set, ignore checking)
|
||||
deadline, _ := ctx.Deadline()
|
||||
// Create a watcher here to reduce memory allocations under lock,
|
||||
// given that memory allocation may trigger GC and block the thread.
|
||||
// Also note that emptyFunc is a placeholder, until we will be able
|
||||
// to compute watcher.forget function (which has to happen under lock).
|
||||
watcher := newCacheWatcher(chanSize, filterWithAttrsFunction(key, pred), emptyFunc, c.versioner, deadline, pred.AllowWatchBookmarks, c.objectType)
|
||||
|
||||
// We explicitly use thread unsafe version and do locking ourself to ensure that
|
||||
// no new events will be processed in the meantime. The watchCache will be unlocked
|
||||
// on return from this function.
|
||||
// Note that we cannot do it under Cacher lock, to avoid a deadlock, since the
|
||||
// underlying watchCache is calling processEvent under its lock.
|
||||
c.watchCache.RLock()
|
||||
defer c.watchCache.RUnlock()
|
||||
initEvents, err := c.watchCache.GetAllEventsSinceThreadUnsafe(watchRV)
|
||||
if err != nil {
|
||||
// To match the uncached watch implementation, once we have passed authn/authz/admission,
|
||||
// and successfully parsed a resource version, other errors must fail with a watch event of type ERROR,
|
||||
// rather than a directly returned error.
|
||||
return newErrWatcher(err), nil
|
||||
}
|
||||
|
||||
// With some events already sent, update resourceVersion so that
|
||||
// events that were buffered and not yet processed won't be delivered
|
||||
// to this watcher second time causing going back in time.
|
||||
|
|
@ -384,13 +469,21 @@ func (c *Cacher) Watch(ctx context.Context, key string, resourceVersion string,
|
|||
watchRV = initEvents[len(initEvents)-1].ResourceVersion
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
forget := forgetWatcher(c, c.watcherIdx, triggerValue, triggerSupported)
|
||||
watcher := newCacheWatcher(watchRV, chanSize, initEvents, filterWithAttrsFunction(key, pred), forget, c.versioner)
|
||||
func() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
// Update watcher.forget function once we can compute it.
|
||||
watcher.forget = forgetWatcher(c, c.watcherIdx, triggerValue, triggerSupported)
|
||||
c.watchers.addWatcher(watcher, c.watcherIdx, triggerValue, triggerSupported)
|
||||
|
||||
c.watchers.addWatcher(watcher, c.watcherIdx, triggerValue, triggerSupported)
|
||||
c.watcherIdx++
|
||||
// Add it to the queue only when server and client support watch bookmarks.
|
||||
if c.watchBookmarkEnabled && watcher.allowWatchBookmarks {
|
||||
c.bookmarkWatchers.addWatcher(watcher)
|
||||
}
|
||||
c.watcherIdx++
|
||||
}()
|
||||
|
||||
go watcher.process(ctx, initEvents, watchRV)
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
|
|
@ -512,7 +605,7 @@ func (c *Cacher) GetToList(ctx context.Context, key string, resourceVersion stri
|
|||
}
|
||||
}
|
||||
if c.versioner != nil {
|
||||
if err := c.versioner.UpdateList(listObj, readResourceVersion, ""); err != nil {
|
||||
if err := c.versioner.UpdateList(listObj, readResourceVersion, "", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -587,7 +680,7 @@ func (c *Cacher) List(ctx context.Context, key string, resourceVersion string, p
|
|||
}
|
||||
trace.Step(fmt.Sprintf("Filtered %d items", listVal.Len()))
|
||||
if c.versioner != nil {
|
||||
if err := c.versioner.UpdateList(listObj, readResourceVersion, ""); err != nil {
|
||||
if err := c.versioner.UpdateList(listObj, readResourceVersion, "", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -650,6 +743,15 @@ func (c *Cacher) processEvent(event *watchCacheEvent) {
|
|||
}
|
||||
|
||||
func (c *Cacher) dispatchEvents() {
|
||||
// Jitter to help level out any aggregate load.
|
||||
bookmarkTimer := c.clock.NewTimer(wait.Jitter(time.Second, 0.25))
|
||||
// Stop the timer when watchBookmarkFeatureGate is not enabled.
|
||||
if !c.watchBookmarkEnabled && !bookmarkTimer.Stop() {
|
||||
<-bookmarkTimer.C()
|
||||
}
|
||||
defer bookmarkTimer.Stop()
|
||||
|
||||
lastProcessedResourceVersion := uint64(0)
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-c.incoming:
|
||||
|
|
@ -657,6 +759,24 @@ func (c *Cacher) dispatchEvents() {
|
|||
return
|
||||
}
|
||||
c.dispatchEvent(&event)
|
||||
lastProcessedResourceVersion = event.ResourceVersion
|
||||
case <-bookmarkTimer.C():
|
||||
bookmarkTimer.Reset(wait.Jitter(time.Second, 0.25))
|
||||
// Never send a bookmark event if we did not see an event here, this is fine
|
||||
// because we don't provide any guarantees on sending bookmarks.
|
||||
if lastProcessedResourceVersion == 0 {
|
||||
continue
|
||||
}
|
||||
bookmarkEvent := &watchCacheEvent{
|
||||
Type: watch.Bookmark,
|
||||
Object: c.newFunc(),
|
||||
ResourceVersion: lastProcessedResourceVersion,
|
||||
}
|
||||
if err := c.versioner.UpdateObject(bookmarkEvent.Object, bookmarkEvent.ResourceVersion); err != nil {
|
||||
klog.Errorf("failure to set resourceVersion to %d on bookmark event %+v", bookmarkEvent.ResourceVersion, bookmarkEvent.Object)
|
||||
continue
|
||||
}
|
||||
c.dispatchEvent(bookmarkEvent)
|
||||
case <-c.stopCh:
|
||||
return
|
||||
}
|
||||
|
|
@ -665,13 +785,36 @@ func (c *Cacher) dispatchEvents() {
|
|||
|
||||
func (c *Cacher) dispatchEvent(event *watchCacheEvent) {
|
||||
c.startDispatching(event)
|
||||
defer c.finishDispatching()
|
||||
// Watchers stopped after startDispatching will be delayed to finishDispatching,
|
||||
|
||||
// Since add() can block, we explicitly add when cacher is unlocked.
|
||||
for _, watcher := range c.watchersBuffer {
|
||||
watcher.add(event, c.timer, c.dispatchTimeoutBudget)
|
||||
if event.Type == watch.Bookmark {
|
||||
for _, watcher := range c.watchersBuffer {
|
||||
watcher.nonblockingAdd(event)
|
||||
}
|
||||
} else {
|
||||
for _, watcher := range c.watchersBuffer {
|
||||
watcher.add(event, c.timer, c.dispatchTimeoutBudget)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.finishDispatching()
|
||||
func (c *Cacher) startDispatchingBookmarkEvents() {
|
||||
// Pop already expired watchers. However, explicitly ignore stopped ones,
|
||||
// as we don't delete watcher from bookmarkWatchers when it is stopped.
|
||||
for _, watchers := range c.bookmarkWatchers.popExpiredWatchers() {
|
||||
for _, watcher := range watchers {
|
||||
// watcher.stop() is protected by c.Lock()
|
||||
if watcher.stopped {
|
||||
continue
|
||||
}
|
||||
c.watchersBuffer = append(c.watchersBuffer, watcher)
|
||||
// Given that we send bookmark event once at deadline-2s, never push again
|
||||
// after the watcher pops up from the buckets. Once we decide to change the
|
||||
// strategy to more sophisticated, we may need it here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// startDispatching chooses watchers potentially interested in a given event
|
||||
|
|
@ -690,6 +833,12 @@ func (c *Cacher) startDispatching(event *watchCacheEvent) {
|
|||
// gain from avoiding memory allocations is much bigger.
|
||||
c.watchersBuffer = c.watchersBuffer[:0]
|
||||
|
||||
if event.Type == watch.Bookmark {
|
||||
c.startDispatchingBookmarkEvents()
|
||||
// return here to reduce following code indentation and diff
|
||||
return
|
||||
}
|
||||
|
||||
// Iterate over "allWatchers" no matter what the trigger function is.
|
||||
for _, watcher := range c.watchers.allWatchers {
|
||||
c.watchersBuffer = append(c.watchersBuffer, watcher)
|
||||
|
|
@ -753,12 +902,9 @@ func (c *Cacher) isStopped() bool {
|
|||
|
||||
// Stop implements the graceful termination.
|
||||
func (c *Cacher) Stop() {
|
||||
// avoid stopping twice (note: cachers are shared with subresources)
|
||||
if c.isStopped() {
|
||||
return
|
||||
}
|
||||
c.stopLock.Lock()
|
||||
if c.stopped {
|
||||
// avoid stopping twice (note: cachers are shared with subresources)
|
||||
c.stopLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
|
@ -805,7 +951,8 @@ type cacherListerWatcher struct {
|
|||
newListFunc func() runtime.Object
|
||||
}
|
||||
|
||||
func newCacherListerWatcher(storage storage.Interface, resourcePrefix string, newListFunc func() runtime.Object) cache.ListerWatcher {
|
||||
// NewCacherListerWatcher returns a storage.Interface backed ListerWatcher.
|
||||
func NewCacherListerWatcher(storage storage.Interface, resourcePrefix string, newListFunc func() runtime.Object) cache.ListerWatcher {
|
||||
return &cacherListerWatcher{
|
||||
storage: storage,
|
||||
resourcePrefix: resourcePrefix,
|
||||
|
|
@ -816,7 +963,14 @@ func newCacherListerWatcher(storage storage.Interface, resourcePrefix string, ne
|
|||
// Implements cache.ListerWatcher interface.
|
||||
func (lw *cacherListerWatcher) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
list := lw.newListFunc()
|
||||
if err := lw.storage.List(context.TODO(), lw.resourcePrefix, "", storage.Everything, list); err != nil {
|
||||
pred := storage.SelectionPredicate{
|
||||
Label: labels.Everything(),
|
||||
Field: fields.Everything(),
|
||||
Limit: options.Limit,
|
||||
Continue: options.Continue,
|
||||
}
|
||||
|
||||
if err := lw.storage.List(context.TODO(), lw.resourcePrefix, "", pred, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
|
|
@ -877,20 +1031,27 @@ type cacheWatcher struct {
|
|||
stopped bool
|
||||
forget func()
|
||||
versioner storage.Versioner
|
||||
// The watcher will be closed by server after the deadline,
|
||||
// save it here to send bookmark events before that.
|
||||
deadline time.Time
|
||||
allowWatchBookmarks bool
|
||||
// Object type of the cache watcher interests
|
||||
objectType reflect.Type
|
||||
}
|
||||
|
||||
func newCacheWatcher(resourceVersion uint64, chanSize int, initEvents []*watchCacheEvent, filter filterWithAttrsFunc, forget func(), versioner storage.Versioner) *cacheWatcher {
|
||||
watcher := &cacheWatcher{
|
||||
input: make(chan *watchCacheEvent, chanSize),
|
||||
result: make(chan watch.Event, chanSize),
|
||||
done: make(chan struct{}),
|
||||
filter: filter,
|
||||
stopped: false,
|
||||
forget: forget,
|
||||
versioner: versioner,
|
||||
func newCacheWatcher(chanSize int, filter filterWithAttrsFunc, forget func(), versioner storage.Versioner, deadline time.Time, allowWatchBookmarks bool, objectType reflect.Type) *cacheWatcher {
|
||||
return &cacheWatcher{
|
||||
input: make(chan *watchCacheEvent, chanSize),
|
||||
result: make(chan watch.Event, chanSize),
|
||||
done: make(chan struct{}),
|
||||
filter: filter,
|
||||
stopped: false,
|
||||
forget: forget,
|
||||
versioner: versioner,
|
||||
deadline: deadline,
|
||||
allowWatchBookmarks: allowWatchBookmarks,
|
||||
objectType: objectType,
|
||||
}
|
||||
go watcher.process(initEvents, resourceVersion)
|
||||
return watcher
|
||||
}
|
||||
|
||||
// Implements watch.Interface.
|
||||
|
|
@ -903,6 +1064,9 @@ func (c *cacheWatcher) Stop() {
|
|||
c.forget()
|
||||
}
|
||||
|
||||
// TODO(#73958)
|
||||
// stop() is protected by Cacher.Lock(), rename it to
|
||||
// stopThreadUnsafe and remove the sync.Mutex.
|
||||
func (c *cacheWatcher) stop() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
|
@ -913,12 +1077,20 @@ func (c *cacheWatcher) stop() {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) add(event *watchCacheEvent, timer *time.Timer, budget *timeBudget) {
|
||||
// Try to send the event immediately, without blocking.
|
||||
func (c *cacheWatcher) nonblockingAdd(event *watchCacheEvent) bool {
|
||||
// If we can't send it, don't block on it.
|
||||
select {
|
||||
case c.input <- event:
|
||||
return
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) add(event *watchCacheEvent, timer *time.Timer, budget *timeBudget) {
|
||||
// Try to send the event immediately, without blocking.
|
||||
if c.nonblockingAdd(event) {
|
||||
return
|
||||
}
|
||||
|
||||
// OK, block sending, but only for up to <timeout>.
|
||||
|
|
@ -947,8 +1119,20 @@ func (c *cacheWatcher) add(event *watchCacheEvent, timer *time.Timer, budget *ti
|
|||
budget.returnUnused(timeout - time.Since(startTime))
|
||||
}
|
||||
|
||||
// NOTE: sendWatchCacheEvent is assumed to not modify <event> !!!
|
||||
func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) {
|
||||
func (c *cacheWatcher) nextBookmarkTime(now time.Time) (time.Time, bool) {
|
||||
// For now we return 2s before deadline (and maybe +infinity is now already passed this time)
|
||||
// but it gives us extensibility for the future(false when deadline is not set).
|
||||
if c.deadline.IsZero() {
|
||||
return c.deadline, false
|
||||
}
|
||||
return c.deadline.Add(-2 * time.Second), true
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) convertToWatchEvent(event *watchCacheEvent) *watch.Event {
|
||||
if event.Type == watch.Bookmark {
|
||||
return &watch.Event{Type: watch.Bookmark, Object: event.Object.DeepCopyObject()}
|
||||
}
|
||||
|
||||
curObjPasses := event.Type != watch.Deleted && c.filter(event.Key, event.ObjLabels, event.ObjFields)
|
||||
oldObjPasses := false
|
||||
if event.PrevObject != nil {
|
||||
|
|
@ -956,22 +1140,32 @@ func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) {
|
|||
}
|
||||
if !curObjPasses && !oldObjPasses {
|
||||
// Watcher is not interested in that object.
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
var watchEvent watch.Event
|
||||
switch {
|
||||
case curObjPasses && !oldObjPasses:
|
||||
watchEvent = watch.Event{Type: watch.Added, Object: event.Object.DeepCopyObject()}
|
||||
return &watch.Event{Type: watch.Added, Object: event.Object.DeepCopyObject()}
|
||||
case curObjPasses && oldObjPasses:
|
||||
watchEvent = watch.Event{Type: watch.Modified, Object: event.Object.DeepCopyObject()}
|
||||
return &watch.Event{Type: watch.Modified, Object: event.Object.DeepCopyObject()}
|
||||
case !curObjPasses && oldObjPasses:
|
||||
// return a delete event with the previous object content, but with the event's resource version
|
||||
oldObj := event.PrevObject.DeepCopyObject()
|
||||
if err := c.versioner.UpdateObject(oldObj, event.ResourceVersion); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failure to version api object (%d) %#v: %v", event.ResourceVersion, oldObj, err))
|
||||
}
|
||||
watchEvent = watch.Event{Type: watch.Deleted, Object: oldObj}
|
||||
return &watch.Event{Type: watch.Deleted, Object: oldObj}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: sendWatchCacheEvent is assumed to not modify <event> !!!
|
||||
func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) {
|
||||
watchEvent := c.convertToWatchEvent(event)
|
||||
if watchEvent == nil {
|
||||
// Watcher is not interested in that object.
|
||||
return
|
||||
}
|
||||
|
||||
// We need to ensure that if we put event X to the c.result, all
|
||||
|
|
@ -993,12 +1187,12 @@ func (c *cacheWatcher) sendWatchCacheEvent(event *watchCacheEvent) {
|
|||
}
|
||||
|
||||
select {
|
||||
case c.result <- watchEvent:
|
||||
case c.result <- *watchEvent:
|
||||
case <-c.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheWatcher) process(initEvents []*watchCacheEvent, resourceVersion uint64) {
|
||||
func (c *cacheWatcher) process(ctx context.Context, initEvents []*watchCacheEvent, resourceVersion uint64) {
|
||||
defer utilruntime.HandleCrash()
|
||||
|
||||
// Check how long we are processing initEvents.
|
||||
|
|
@ -1019,25 +1213,29 @@ func (c *cacheWatcher) process(initEvents []*watchCacheEvent, resourceVersion ui
|
|||
for _, event := range initEvents {
|
||||
c.sendWatchCacheEvent(event)
|
||||
}
|
||||
objType := c.objectType.String()
|
||||
if len(initEvents) > 0 {
|
||||
objType := reflect.TypeOf(initEvents[0].Object).String()
|
||||
initCounter.WithLabelValues(objType).Add(float64(len(initEvents)))
|
||||
}
|
||||
processingTime := time.Since(startTime)
|
||||
if processingTime > initProcessThreshold {
|
||||
objType := "<null>"
|
||||
if len(initEvents) > 0 {
|
||||
objType = reflect.TypeOf(initEvents[0].Object).String()
|
||||
}
|
||||
klog.V(2).Infof("processing %d initEvents of %s took %v", len(initEvents), objType, processingTime)
|
||||
}
|
||||
|
||||
defer close(c.result)
|
||||
defer c.Stop()
|
||||
for event := range c.input {
|
||||
// only send events newer than resourceVersion
|
||||
if event.ResourceVersion > resourceVersion {
|
||||
c.sendWatchCacheEvent(event)
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-c.input:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// only send events newer than resourceVersion
|
||||
if event.ResourceVersion > resourceVersion {
|
||||
c.sendWatchCacheEvent(event)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1048,7 +1246,7 @@ type ready struct {
|
|||
}
|
||||
|
||||
func newReady() *ready {
|
||||
return &ready{c: sync.NewCond(&sync.Mutex{})}
|
||||
return &ready{c: sync.NewCond(&sync.RWMutex{})}
|
||||
}
|
||||
|
||||
func (r *ready) wait() {
|
||||
|
|
@ -1062,8 +1260,9 @@ func (r *ready) wait() {
|
|||
// TODO: Make check() function more sophisticated, in particular
|
||||
// allow it to behave as "waitWithTimeout".
|
||||
func (r *ready) check() bool {
|
||||
r.c.L.Lock()
|
||||
defer r.c.L.Unlock()
|
||||
rwMutex := r.c.L.(*sync.RWMutex)
|
||||
rwMutex.RLock()
|
||||
defer rwMutex.RUnlock()
|
||||
return r.ok
|
||||
}
|
||||
|
||||
|
|
|
|||
89
vendor/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go
generated
vendored
89
vendor/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go
generated
vendored
|
|
@ -30,6 +30,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
|
|
@ -77,14 +78,6 @@ func storeElementKey(obj interface{}) (string, error) {
|
|||
return elem.Key, nil
|
||||
}
|
||||
|
||||
// watchCacheElement is a single "watch event" stored in a cache.
|
||||
// It contains the resource version of the object and the object
|
||||
// itself.
|
||||
type watchCacheElement struct {
|
||||
resourceVersion uint64
|
||||
watchCacheEvent *watchCacheEvent
|
||||
}
|
||||
|
||||
// watchCache implements a Store interface.
|
||||
// However, it depends on the elements implementing runtime.Object interface.
|
||||
//
|
||||
|
|
@ -111,7 +104,7 @@ type watchCache struct {
|
|||
// by endIndex (if cache is full it will be startIndex + capacity).
|
||||
// Both startIndex and endIndex can be greater than buffer capacity -
|
||||
// you should always apply modulo capacity to get an index in cache array.
|
||||
cache []watchCacheElement
|
||||
cache []*watchCacheEvent
|
||||
startIndex int
|
||||
endIndex int
|
||||
|
||||
|
|
@ -132,7 +125,7 @@ type watchCache struct {
|
|||
|
||||
// This handler is run at the end of every Add/Update/Delete method
|
||||
// and additionally gets the previous value of the object.
|
||||
onEvent func(*watchCacheEvent)
|
||||
eventHandler func(*watchCacheEvent)
|
||||
|
||||
// for testing timeouts.
|
||||
clock clock.Clock
|
||||
|
|
@ -144,18 +137,20 @@ type watchCache struct {
|
|||
func newWatchCache(
|
||||
capacity int,
|
||||
keyFunc func(runtime.Object) (string, error),
|
||||
eventHandler func(*watchCacheEvent),
|
||||
getAttrsFunc func(runtime.Object) (labels.Set, fields.Set, error),
|
||||
versioner storage.Versioner) *watchCache {
|
||||
wc := &watchCache{
|
||||
capacity: capacity,
|
||||
keyFunc: keyFunc,
|
||||
getAttrsFunc: getAttrsFunc,
|
||||
cache: make([]watchCacheElement, capacity),
|
||||
cache: make([]*watchCacheEvent, capacity),
|
||||
startIndex: 0,
|
||||
endIndex: 0,
|
||||
store: cache.NewStore(storeElementKey),
|
||||
resourceVersion: 0,
|
||||
listResourceVersion: 0,
|
||||
eventHandler: eventHandler,
|
||||
clock: clock.RealClock{},
|
||||
versioner: versioner,
|
||||
}
|
||||
|
|
@ -211,6 +206,8 @@ func (w *watchCache) objectToVersionedRuntimeObject(obj interface{}) (runtime.Ob
|
|||
return object, resourceVersion, nil
|
||||
}
|
||||
|
||||
// processEvent is safe as long as there is at most one call to it in flight
|
||||
// at any point in time.
|
||||
func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, updateFunc func(*storeElement) error) error {
|
||||
key, err := w.keyFunc(event.Object)
|
||||
if err != nil {
|
||||
|
|
@ -231,39 +228,50 @@ func (w *watchCache) processEvent(event watch.Event, resourceVersion uint64, upd
|
|||
ResourceVersion: resourceVersion,
|
||||
}
|
||||
|
||||
// TODO: We should consider moving this lock below after the watchCacheEvent
|
||||
// is created. In such situation, the only problematic scenario is Replace(
|
||||
// happening after getting object from store and before acquiring a lock.
|
||||
// Maybe introduce another lock for this purpose.
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
previous, exists, err := w.store.Get(elem)
|
||||
if err != nil {
|
||||
if err := func() error {
|
||||
// TODO: We should consider moving this lock below after the watchCacheEvent
|
||||
// is created. In such situation, the only problematic scenario is Replace(
|
||||
// happening after getting object from store and before acquiring a lock.
|
||||
// Maybe introduce another lock for this purpose.
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
previous, exists, err := w.store.Get(elem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
previousElem := previous.(*storeElement)
|
||||
watchCacheEvent.PrevObject = previousElem.Object
|
||||
watchCacheEvent.PrevObjLabels = previousElem.Labels
|
||||
watchCacheEvent.PrevObjFields = previousElem.Fields
|
||||
}
|
||||
|
||||
w.updateCache(watchCacheEvent)
|
||||
w.resourceVersion = resourceVersion
|
||||
defer w.cond.Broadcast()
|
||||
|
||||
return updateFunc(elem)
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
previousElem := previous.(*storeElement)
|
||||
watchCacheEvent.PrevObject = previousElem.Object
|
||||
watchCacheEvent.PrevObjLabels = previousElem.Labels
|
||||
watchCacheEvent.PrevObjFields = previousElem.Fields
|
||||
}
|
||||
|
||||
if w.onEvent != nil {
|
||||
w.onEvent(watchCacheEvent)
|
||||
// Avoid calling event handler under lock.
|
||||
// This is safe as long as there is at most one call to processEvent in flight
|
||||
// at any point in time.
|
||||
if w.eventHandler != nil {
|
||||
w.eventHandler(watchCacheEvent)
|
||||
}
|
||||
w.updateCache(resourceVersion, watchCacheEvent)
|
||||
w.resourceVersion = resourceVersion
|
||||
w.cond.Broadcast()
|
||||
return updateFunc(elem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assumes that lock is already held for write.
|
||||
func (w *watchCache) updateCache(resourceVersion uint64, event *watchCacheEvent) {
|
||||
func (w *watchCache) updateCache(event *watchCacheEvent) {
|
||||
if w.endIndex == w.startIndex+w.capacity {
|
||||
// Cache is full - remove the oldest element.
|
||||
w.startIndex++
|
||||
}
|
||||
w.cache[w.endIndex%w.capacity] = watchCacheElement{resourceVersion, event}
|
||||
w.cache[w.endIndex%w.capacity] = event
|
||||
w.endIndex++
|
||||
}
|
||||
|
||||
|
|
@ -394,6 +402,7 @@ func (w *watchCache) Replace(objs []interface{}, resourceVersion string) error {
|
|||
w.onReplace()
|
||||
}
|
||||
w.cond.Broadcast()
|
||||
klog.V(3).Infof("Replace watchCache (rev: %v) ", resourceVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -403,12 +412,6 @@ func (w *watchCache) SetOnReplace(onReplace func()) {
|
|||
w.onReplace = onReplace
|
||||
}
|
||||
|
||||
func (w *watchCache) SetOnEvent(onEvent func(*watchCacheEvent)) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
w.onEvent = onEvent
|
||||
}
|
||||
|
||||
func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*watchCacheEvent, error) {
|
||||
size := w.endIndex - w.startIndex
|
||||
var oldest uint64
|
||||
|
|
@ -416,7 +419,7 @@ func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*w
|
|||
case size >= w.capacity:
|
||||
// Once the watch event buffer is full, the oldest watch event we can deliver
|
||||
// is the first one in the buffer.
|
||||
oldest = w.cache[w.startIndex%w.capacity].resourceVersion
|
||||
oldest = w.cache[w.startIndex%w.capacity].ResourceVersion
|
||||
case w.listResourceVersion > 0:
|
||||
// If the watch event buffer isn't full, the oldest watch event we can deliver
|
||||
// is one greater than the resource version of the last full list.
|
||||
|
|
@ -426,7 +429,7 @@ func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*w
|
|||
// in the buffer.
|
||||
// This should only happen in unit tests that populate the buffer without
|
||||
// performing list/replace operations.
|
||||
oldest = w.cache[w.startIndex%w.capacity].resourceVersion
|
||||
oldest = w.cache[w.startIndex%w.capacity].ResourceVersion
|
||||
default:
|
||||
return nil, fmt.Errorf("watch cache isn't correctly initialized")
|
||||
}
|
||||
|
|
@ -466,12 +469,12 @@ func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*w
|
|||
|
||||
// Binary search the smallest index at which resourceVersion is greater than the given one.
|
||||
f := func(i int) bool {
|
||||
return w.cache[(w.startIndex+i)%w.capacity].resourceVersion > resourceVersion
|
||||
return w.cache[(w.startIndex+i)%w.capacity].ResourceVersion > resourceVersion
|
||||
}
|
||||
first := sort.Search(size, f)
|
||||
result := make([]*watchCacheEvent, size-first)
|
||||
for i := 0; i < size-first; i++ {
|
||||
result[i] = w.cache[(w.startIndex+first+i)%w.capacity].watchCacheEvent
|
||||
result[i] = w.cache[(w.startIndex+first+i)%w.capacity]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
3
vendor/k8s.io/apiserver/pkg/storage/etcd/api_object_versioner.go
generated
vendored
3
vendor/k8s.io/apiserver/pkg/storage/etcd/api_object_versioner.go
generated
vendored
|
|
@ -44,7 +44,7 @@ func (a APIObjectVersioner) UpdateObject(obj runtime.Object, resourceVersion uin
|
|||
}
|
||||
|
||||
// UpdateList implements Versioner
|
||||
func (a APIObjectVersioner) UpdateList(obj runtime.Object, resourceVersion uint64, nextKey string) error {
|
||||
func (a APIObjectVersioner) UpdateList(obj runtime.Object, resourceVersion uint64, nextKey string, count *int64) error {
|
||||
listAccessor, err := meta.ListAccessor(obj)
|
||||
if err != nil || listAccessor == nil {
|
||||
return err
|
||||
|
|
@ -55,6 +55,7 @@ func (a APIObjectVersioner) UpdateList(obj runtime.Object, resourceVersion uint6
|
|||
}
|
||||
listAccessor.SetResourceVersion(versionString)
|
||||
listAccessor.SetContinue(nextKey)
|
||||
listAccessor.SetRemainingItemCount(count)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
10
vendor/k8s.io/apiserver/pkg/storage/etcd3/event.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/storage/etcd3/event.go
generated
vendored
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package etcd3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
|
@ -42,7 +43,12 @@ func parseKV(kv *mvccpb.KeyValue) *event {
|
|||
}
|
||||
}
|
||||
|
||||
func parseEvent(e *clientv3.Event) *event {
|
||||
func parseEvent(e *clientv3.Event) (*event, error) {
|
||||
if !e.IsCreate() && e.PrevKv == nil {
|
||||
// If the previous value is nil, error. One example of how this is possible is if the previous value has been compacted already.
|
||||
return nil, fmt.Errorf("etcd event received with PrevKv=nil (key=%q, modRevision=%d, type=%s)", string(e.Kv.Key), e.Kv.ModRevision, e.Type.String())
|
||||
|
||||
}
|
||||
ret := &event{
|
||||
key: string(e.Kv.Key),
|
||||
value: e.Kv.Value,
|
||||
|
|
@ -53,5 +59,5 @@ func parseEvent(e *clientv3.Event) *event {
|
|||
if e.PrevKv != nil {
|
||||
ret.prevValue = e.PrevKv.Value
|
||||
}
|
||||
return ret
|
||||
return ret, nil
|
||||
}
|
||||
|
|
|
|||
136
vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go
generated
vendored
136
vendor/k8s.io/apiserver/pkg/storage/etcd3/store.go
generated
vendored
|
|
@ -36,9 +36,12 @@ import (
|
|||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/etcd"
|
||||
"k8s.io/apiserver/pkg/storage/etcd/metrics"
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
|
|
@ -111,7 +114,9 @@ func (s *store) Versioner() storage.Versioner {
|
|||
// Get implements storage.Interface.Get.
|
||||
func (s *store) Get(ctx context.Context, key string, resourceVersion string, out runtime.Object, ignoreNotFound bool) error {
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(ctx, key, s.getOps...)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -156,11 +161,13 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
|||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
txnResp, err := s.client.KV.Txn(ctx).If(
|
||||
notFound(key),
|
||||
).Then(
|
||||
clientv3.OpPut(key, string(newData), opts...),
|
||||
).Commit()
|
||||
metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -176,43 +183,19 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
|||
}
|
||||
|
||||
// Delete implements storage.Interface.Delete.
|
||||
func (s *store) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions) error {
|
||||
func (s *store) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error {
|
||||
v, err := conversion.EnforcePtr(out)
|
||||
if err != nil {
|
||||
panic("unable to convert output object to pointer")
|
||||
}
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
if preconditions == nil {
|
||||
return s.unconditionalDelete(ctx, key, out)
|
||||
}
|
||||
return s.conditionalDelete(ctx, key, out, v, preconditions)
|
||||
return s.conditionalDelete(ctx, key, out, v, preconditions, validateDeletion)
|
||||
}
|
||||
|
||||
func (s *store) unconditionalDelete(ctx context.Context, key string, out runtime.Object) error {
|
||||
// We need to do get and delete in single transaction in order to
|
||||
// know the value and revision before deleting it.
|
||||
txnResp, err := s.client.KV.Txn(ctx).If().Then(
|
||||
clientv3.OpGet(key),
|
||||
clientv3.OpDelete(key),
|
||||
).Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
getResp := txnResp.Responses[0].GetResponseRange()
|
||||
if len(getResp.Kvs) == 0 {
|
||||
return storage.NewKeyNotFoundError(key, 0)
|
||||
}
|
||||
|
||||
kv := getResp.Kvs[0]
|
||||
data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(key))
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
return decode(s.codec, s.versioner, data, out, kv.ModRevision)
|
||||
}
|
||||
|
||||
func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.Object, v reflect.Value, preconditions *storage.Preconditions) error {
|
||||
func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.Object, v reflect.Value, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error {
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(ctx, key)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -221,9 +204,15 @@ func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.O
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := preconditions.Check(key, origState.obj); err != nil {
|
||||
if preconditions != nil {
|
||||
if err := preconditions.Check(key, origState.obj); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := validateDeletion(origState.obj); err != nil {
|
||||
return err
|
||||
}
|
||||
startTime := time.Now()
|
||||
txnResp, err := s.client.KV.Txn(ctx).If(
|
||||
clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev),
|
||||
).Then(
|
||||
|
|
@ -231,6 +220,7 @@ func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.O
|
|||
).Else(
|
||||
clientv3.OpGet(key),
|
||||
).Commit()
|
||||
metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -247,7 +237,7 @@ func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.O
|
|||
func (s *store) GuaranteedUpdate(
|
||||
ctx context.Context, key string, out runtime.Object, ignoreNotFound bool,
|
||||
preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, suggestion ...runtime.Object) error {
|
||||
trace := utiltrace.New(fmt.Sprintf("GuaranteedUpdate etcd3: %s", reflect.TypeOf(out).String()))
|
||||
trace := utiltrace.New(fmt.Sprintf("GuaranteedUpdate etcd3: %s", getTypeName(out)))
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
v, err := conversion.EnforcePtr(out)
|
||||
|
|
@ -257,7 +247,9 @@ func (s *store) GuaranteedUpdate(
|
|||
key = path.Join(s.pathPrefix, key)
|
||||
|
||||
getCurrentState := func() (*objState, error) {
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(ctx, key, s.getOps...)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -283,24 +275,38 @@ func (s *store) GuaranteedUpdate(
|
|||
transformContext := authenticatedDataString(key)
|
||||
for {
|
||||
if err := preconditions.Check(key, origState.obj); err != nil {
|
||||
return err
|
||||
// If our data is already up to date, return the error
|
||||
if !mustCheckData {
|
||||
return err
|
||||
}
|
||||
|
||||
// It's possible we were working with stale data
|
||||
// Actually fetch
|
||||
origState, err = getCurrentState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mustCheckData = false
|
||||
// Retry
|
||||
continue
|
||||
}
|
||||
|
||||
ret, ttl, err := s.updateState(origState, tryUpdate)
|
||||
if err != nil {
|
||||
// It's possible we were working with stale data
|
||||
if mustCheckData && apierrors.IsConflict(err) {
|
||||
// Actually fetch
|
||||
origState, err = getCurrentState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mustCheckData = false
|
||||
// Retry
|
||||
continue
|
||||
// If our data is already up to date, return the error
|
||||
if !mustCheckData {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
// It's possible we were working with stale data
|
||||
// Actually fetch
|
||||
origState, err = getCurrentState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mustCheckData = false
|
||||
// Retry
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := runtime.Encode(s.codec, ret)
|
||||
|
|
@ -339,6 +345,7 @@ func (s *store) GuaranteedUpdate(
|
|||
}
|
||||
trace.Step("Transaction prepared")
|
||||
|
||||
startTime := time.Now()
|
||||
txnResp, err := s.client.KV.Txn(ctx).If(
|
||||
clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev),
|
||||
).Then(
|
||||
|
|
@ -346,6 +353,7 @@ func (s *store) GuaranteedUpdate(
|
|||
).Else(
|
||||
clientv3.OpGet(key),
|
||||
).Commit()
|
||||
metrics.RecordEtcdRequestLatency("update", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -369,6 +377,8 @@ func (s *store) GuaranteedUpdate(
|
|||
|
||||
// GetToList implements storage.Interface.GetToList.
|
||||
func (s *store) GetToList(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error {
|
||||
trace := utiltrace.New(fmt.Sprintf("GetToList etcd3: key=%v, resourceVersion=%s, limit: %d, continue: %s", key, resourceVersion, pred.Limit, pred.Continue))
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
listPtr, err := meta.GetItemsPtr(listObj)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -379,7 +389,9 @@ func (s *store) GetToList(ctx context.Context, key string, resourceVersion strin
|
|||
}
|
||||
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(ctx, key, s.getOps...)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -394,12 +406,14 @@ func (s *store) GetToList(ctx context.Context, key string, resourceVersion strin
|
|||
}
|
||||
}
|
||||
// update version with cluster level revision
|
||||
return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision), "")
|
||||
return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision), "", nil)
|
||||
}
|
||||
|
||||
func (s *store) Count(key string) (int64, error) {
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(context.Background(), key, clientv3.WithRange(clientv3.GetPrefixRangeEnd(key)), clientv3.WithCountOnly())
|
||||
metrics.RecordEtcdRequestLatency("listWithCount", key, startTime)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -468,6 +482,8 @@ func encodeContinue(key, keyPrefix string, resourceVersion int64) (string, error
|
|||
|
||||
// List implements storage.Interface.List.
|
||||
func (s *store) List(ctx context.Context, key, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error {
|
||||
trace := utiltrace.New(fmt.Sprintf("List etcd3: key=%v, resourceVersion=%s, limit: %d, continue: %s", key, resourceVersion, pred.Limit, pred.Continue))
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
listPtr, err := meta.GetItemsPtr(listObj)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -553,8 +569,11 @@ func (s *store) List(ctx context.Context, key, resourceVersion string, pred stor
|
|||
// loop until we have filled the requested limit from etcd or there are no more results
|
||||
var lastKey []byte
|
||||
var hasMore bool
|
||||
var getResp *clientv3.GetResponse
|
||||
for {
|
||||
getResp, err := s.client.KV.Get(ctx, key, options...)
|
||||
startTime := time.Now()
|
||||
getResp, err = s.client.KV.Get(ctx, key, options...)
|
||||
metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime)
|
||||
if err != nil {
|
||||
return interpretListError(err, len(pred.Continue) > 0, continueKey, keyPrefix)
|
||||
}
|
||||
|
|
@ -614,11 +633,21 @@ func (s *store) List(ctx context.Context, key, resourceVersion string, pred stor
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.versioner.UpdateList(listObj, uint64(returnedRV), next)
|
||||
var remainingItemCount *int64
|
||||
// getResp.Count counts in objects that do not match the pred.
|
||||
// Instead of returning inaccurate count for non-empty selectors, we return nil.
|
||||
// Only set remainingItemCount if the predicate is empty.
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.RemainingItemCount) {
|
||||
if pred.Empty() {
|
||||
c := int64(getResp.Count - pred.Limit)
|
||||
remainingItemCount = &c
|
||||
}
|
||||
}
|
||||
return s.versioner.UpdateList(listObj, uint64(returnedRV), next, remainingItemCount)
|
||||
}
|
||||
|
||||
// no continuation
|
||||
return s.versioner.UpdateList(listObj, uint64(returnedRV), "")
|
||||
return s.versioner.UpdateList(listObj, uint64(returnedRV), "", nil)
|
||||
}
|
||||
|
||||
// growSlice takes a slice value and grows its capacity up
|
||||
|
|
@ -673,9 +702,15 @@ func (s *store) watch(ctx context.Context, key string, rv string, pred storage.S
|
|||
|
||||
func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) {
|
||||
state := &objState{
|
||||
obj: reflect.New(v.Type()).Interface().(runtime.Object),
|
||||
meta: &storage.ResponseMeta{},
|
||||
}
|
||||
|
||||
if u, ok := v.Addr().Interface().(runtime.Unstructured); ok {
|
||||
state.obj = u.NewEmptyInstance()
|
||||
} else {
|
||||
state.obj = reflect.New(v.Type()).Interface().(runtime.Object)
|
||||
}
|
||||
|
||||
if len(getResp.Kvs) == 0 {
|
||||
if !ignoreNotFound {
|
||||
return nil, storage.NewKeyNotFoundError(key, 0)
|
||||
|
|
@ -786,3 +821,8 @@ func appendListItem(v reflect.Value, data []byte, rev uint64, pred storage.Selec
|
|||
func notFound(key string) clientv3.Cmp {
|
||||
return clientv3.Compare(clientv3.ModRevision(key), "=", 0)
|
||||
}
|
||||
|
||||
// getTypeName returns type name of an object for reporting purposes.
|
||||
func getTypeName(obj interface{}) string {
|
||||
return reflect.TypeOf(obj).String()
|
||||
}
|
||||
|
|
|
|||
14
vendor/k8s.io/apiserver/pkg/storage/etcd3/watcher.go
generated
vendored
14
vendor/k8s.io/apiserver/pkg/storage/etcd3/watcher.go
generated
vendored
|
|
@ -56,9 +56,15 @@ func testingDeferOnDecodeError() {
|
|||
|
||||
func init() {
|
||||
// check to see if we are running in a test environment
|
||||
TestOnlySetFatalOnDecodeError(true)
|
||||
fatalOnDecodeError, _ = strconv.ParseBool(os.Getenv("KUBE_PANIC_WATCH_DECODE_ERROR"))
|
||||
}
|
||||
|
||||
// TestOnlySetFatalOnDecodeError should only be used for cases where decode errors are expected and need to be tested. e.g. conversion webhooks.
|
||||
func TestOnlySetFatalOnDecodeError(b bool) {
|
||||
fatalOnDecodeError = b
|
||||
}
|
||||
|
||||
type watcher struct {
|
||||
client *clientv3.Client
|
||||
codec runtime.Codec
|
||||
|
|
@ -210,7 +216,13 @@ func (wc *watchChan) startWatching(watchClosedCh chan struct{}) {
|
|||
return
|
||||
}
|
||||
for _, e := range wres.Events {
|
||||
wc.sendEvent(parseEvent(e))
|
||||
parsedEvent, err := parseEvent(e)
|
||||
if err != nil {
|
||||
klog.Errorf("watch chan error: %v", err)
|
||||
wc.sendError(err)
|
||||
return
|
||||
}
|
||||
wc.sendEvent(parsedEvent)
|
||||
}
|
||||
}
|
||||
// When we come to this point, it's only possible that client side ends the watch.
|
||||
|
|
|
|||
26
vendor/k8s.io/apiserver/pkg/storage/interfaces.go
generated
vendored
26
vendor/k8s.io/apiserver/pkg/storage/interfaces.go
generated
vendored
|
|
@ -40,10 +40,12 @@ type Versioner interface {
|
|||
// from database.
|
||||
UpdateObject(obj runtime.Object, resourceVersion uint64) error
|
||||
// UpdateList sets the resource version into an API list object. Returns an error if the object
|
||||
// cannot be updated correctly. May return nil if the requested object does not need metadata
|
||||
// from database. continueValue is optional and indicates that more results are available if
|
||||
// the client passes that value to the server in a subsequent call.
|
||||
UpdateList(obj runtime.Object, resourceVersion uint64, continueValue string) error
|
||||
// cannot be updated correctly. May return nil if the requested object does not need metadata from
|
||||
// database. continueValue is optional and indicates that more results are available if the client
|
||||
// passes that value to the server in a subsequent call. remainingItemCount indicates the number
|
||||
// of remaining objects if the list is partial. The remainingItemCount field is omitted during
|
||||
// serialization if it is set to nil.
|
||||
UpdateList(obj runtime.Object, resourceVersion uint64, continueValue string, remainingItemCount *int64) error
|
||||
// PrepareObjectForStorage should set SelfLink and ResourceVersion to the empty value. Should
|
||||
// return an error if the specified object cannot be updated.
|
||||
PrepareObjectForStorage(obj runtime.Object) error
|
||||
|
|
@ -93,6 +95,16 @@ var Everything = SelectionPredicate{
|
|||
// See the comment for GuaranteedUpdate for more details.
|
||||
type UpdateFunc func(input runtime.Object, res ResponseMeta) (output runtime.Object, ttl *uint64, err error)
|
||||
|
||||
// ValidateObjectFunc is a function to act on a given object. An error may be returned
|
||||
// if the hook cannot be completed. The function may NOT transform the provided
|
||||
// object.
|
||||
type ValidateObjectFunc func(obj runtime.Object) error
|
||||
|
||||
// ValidateAllObjectFunc is a "admit everything" instance of ValidateObjectFunc.
|
||||
func ValidateAllObjectFunc(obj runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
|
||||
type Preconditions struct {
|
||||
// Specifies the target UID.
|
||||
|
|
@ -151,7 +163,7 @@ type Interface interface {
|
|||
|
||||
// Delete removes the specified key and returns the value that existed at that spot.
|
||||
// If key didn't exist, it will return NotFound storage error.
|
||||
Delete(ctx context.Context, key string, out runtime.Object, preconditions *Preconditions) error
|
||||
Delete(ctx context.Context, key string, out runtime.Object, preconditions *Preconditions, validateDeletion ValidateObjectFunc) error
|
||||
|
||||
// Watch begins watching the specified key. Events are decoded into API objects,
|
||||
// and any items selected by 'p' are sent down to returned watch.Interface.
|
||||
|
|
@ -219,8 +231,8 @@ type Interface interface {
|
|||
// // Return the modified object - return an error to stop iterating. Return
|
||||
// // a uint64 to alter the TTL on the object, or nil to keep it the same value.
|
||||
// return cur, nil, nil
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
// )
|
||||
GuaranteedUpdate(
|
||||
ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool,
|
||||
precondtions *Preconditions, tryUpdate UpdateFunc, suggestion ...runtime.Object) error
|
||||
|
|
|
|||
13
vendor/k8s.io/apiserver/pkg/storage/selection_predicate.go
generated
vendored
13
vendor/k8s.io/apiserver/pkg/storage/selection_predicate.go
generated
vendored
|
|
@ -71,12 +71,13 @@ func (f AttrFunc) WithFieldMutation(fieldMutator FieldMutationFunc) AttrFunc {
|
|||
|
||||
// SelectionPredicate is used to represent the way to select objects from api storage.
|
||||
type SelectionPredicate struct {
|
||||
Label labels.Selector
|
||||
Field fields.Selector
|
||||
GetAttrs AttrFunc
|
||||
IndexFields []string
|
||||
Limit int64
|
||||
Continue string
|
||||
Label labels.Selector
|
||||
Field fields.Selector
|
||||
GetAttrs AttrFunc
|
||||
IndexFields []string
|
||||
Limit int64
|
||||
Continue string
|
||||
AllowWatchBookmarks bool
|
||||
}
|
||||
|
||||
// Matches returns true if the given object's labels and fields (as
|
||||
|
|
|
|||
3
vendor/k8s.io/apiserver/pkg/storage/storagebackend/config.go
generated
vendored
3
vendor/k8s.io/apiserver/pkg/storage/storagebackend/config.go
generated
vendored
|
|
@ -48,8 +48,6 @@ type Config struct {
|
|||
Prefix string
|
||||
// Transport holds all connection related info, i.e. equal TransportConfig means equal servers we talk to.
|
||||
Transport TransportConfig
|
||||
// Quorum indicates that whether read operations should be quorum-level consistent.
|
||||
Quorum bool
|
||||
// Paging indicates whether the server implementation should allow paging (if it is
|
||||
// supported). This is generally configured by feature gating, or by a specific
|
||||
// resource type not wishing to allow paging, and is not intended for end users to
|
||||
|
|
@ -74,6 +72,7 @@ type Config struct {
|
|||
|
||||
func NewDefaultConfig(prefix string, codec runtime.Codec) *Config {
|
||||
return &Config{
|
||||
Paging: true,
|
||||
Prefix: prefix,
|
||||
Codec: codec,
|
||||
CompactionInterval: DefaultCompactInterval,
|
||||
|
|
|
|||
37
vendor/k8s.io/apiserver/pkg/storage/value/metrics.go
generated
vendored
37
vendor/k8s.io/apiserver/pkg/storage/value/metrics.go
generated
vendored
|
|
@ -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.
|
||||
|
|
@ -20,6 +20,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
|
|
@ -53,12 +55,23 @@ var (
|
|||
},
|
||||
[]string{"transformation_type"},
|
||||
)
|
||||
transformerFailuresTotal = prometheus.NewCounterVec(
|
||||
|
||||
transformerOperationsTotal = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "transformation_operations_total",
|
||||
Help: "Total number of transformations.",
|
||||
},
|
||||
[]string{"transformation_type", "status"},
|
||||
)
|
||||
|
||||
deprecatedTransformerFailuresTotal = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "transformation_failures_total",
|
||||
Help: "Total number of failed transformation operations.",
|
||||
Help: "(Deprecated) Total number of failed transformation operations.",
|
||||
},
|
||||
[]string{"transformation_type"},
|
||||
)
|
||||
|
|
@ -106,7 +119,8 @@ func RegisterMetrics() {
|
|||
registerMetrics.Do(func() {
|
||||
prometheus.MustRegister(transformerLatencies)
|
||||
prometheus.MustRegister(deprecatedTransformerLatencies)
|
||||
prometheus.MustRegister(transformerFailuresTotal)
|
||||
prometheus.MustRegister(transformerOperationsTotal)
|
||||
prometheus.MustRegister(deprecatedTransformerFailuresTotal)
|
||||
prometheus.MustRegister(envelopeTransformationCacheMissTotal)
|
||||
prometheus.MustRegister(dataKeyGenerationLatencies)
|
||||
prometheus.MustRegister(deprecatedDataKeyGenerationLatencies)
|
||||
|
|
@ -115,14 +129,17 @@ func RegisterMetrics() {
|
|||
}
|
||||
|
||||
// RecordTransformation records latencies and count of TransformFromStorage and TransformToStorage operations.
|
||||
// Note that transformation_failures_total metric is deprecated, use transformation_operations_total instead.
|
||||
func RecordTransformation(transformationType string, start time.Time, err error) {
|
||||
if err != nil {
|
||||
transformerFailuresTotal.WithLabelValues(transformationType).Inc()
|
||||
return
|
||||
}
|
||||
transformerOperationsTotal.WithLabelValues(transformationType, status.Code(err).String()).Inc()
|
||||
|
||||
transformerLatencies.WithLabelValues(transformationType).Observe(sinceInSeconds(start))
|
||||
deprecatedTransformerLatencies.WithLabelValues(transformationType).Observe(sinceInMicroseconds(start))
|
||||
switch {
|
||||
case err == nil:
|
||||
transformerLatencies.WithLabelValues(transformationType).Observe(sinceInSeconds(start))
|
||||
deprecatedTransformerLatencies.WithLabelValues(transformationType).Observe(sinceInMicroseconds(start))
|
||||
default:
|
||||
deprecatedTransformerFailuresTotal.WithLabelValues(transformationType).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
// RecordCacheMiss records a miss on Key Encryption Key(KEK) - call to KMS was required to decrypt KEK.
|
||||
|
|
|
|||
42
vendor/k8s.io/apiserver/pkg/storage/value/transformer.go
generated
vendored
42
vendor/k8s.io/apiserver/pkg/storage/value/transformer.go
generated
vendored
|
|
@ -22,6 +22,8 @@ import (
|
|||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -134,6 +136,7 @@ func NewPrefixTransformers(err error, transformers ...PrefixTransformer) Transfo
|
|||
// the result of transforming the value. It will always mark any transformation as stale that is not using
|
||||
// the first transformer.
|
||||
func (t *prefixTransformers) TransformFromStorage(data []byte, context Context) ([]byte, bool, error) {
|
||||
var errs []error
|
||||
for i, transformer := range t.transformers {
|
||||
if bytes.HasPrefix(data, transformer.Prefix) {
|
||||
result, stale, err := transformer.Transformer.TransformFromStorage(data[len(transformer.Prefix):], context)
|
||||
|
|
@ -144,9 +147,48 @@ func (t *prefixTransformers) TransformFromStorage(data []byte, context Context)
|
|||
if len(transformer.Prefix) == 0 && err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// It is valid to have overlapping prefixes when the same encryption provider
|
||||
// is specified multiple times but with different keys (the first provider is
|
||||
// being rotated to and some later provider is being rotated away from).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// {
|
||||
// "aescbc": {
|
||||
// "keys": [
|
||||
// {
|
||||
// "name": "2",
|
||||
// "secret": "some key 2"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "aescbc": {
|
||||
// "keys": [
|
||||
// {
|
||||
// "name": "1",
|
||||
// "secret": "some key 1"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// The transformers for both aescbc configs share the prefix k8s:enc:aescbc:v1:
|
||||
// but a failure in the first one should not prevent a later match from being attempted.
|
||||
// Thus we never short-circuit on a prefix match that results in an error.
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
return result, stale || i != 0, err
|
||||
}
|
||||
}
|
||||
if err := errors.Reduce(errors.NewAggregate(errs)); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, false, t.err
|
||||
}
|
||||
|
||||
|
|
|
|||
320
vendor/k8s.io/apiserver/pkg/util/feature/feature_gate.go
generated
vendored
320
vendor/k8s.io/apiserver/pkg/util/feature/feature_gate.go
generated
vendored
|
|
@ -17,327 +17,17 @@ limitations under the License.
|
|||
package feature
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
type Feature string
|
||||
|
||||
const (
|
||||
flagName = "feature-gates"
|
||||
|
||||
// allAlphaGate is a global toggle for alpha features. Per-feature key
|
||||
// values override the default set by allAlphaGate. Examples:
|
||||
// AllAlpha=false,NewFeature=true will result in newFeature=true
|
||||
// AllAlpha=true,NewFeature=false will result in newFeature=false
|
||||
allAlphaGate Feature = "AllAlpha"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
var (
|
||||
// The generic features.
|
||||
defaultFeatures = map[Feature]FeatureSpec{
|
||||
allAlphaGate: {Default: false, PreRelease: Alpha},
|
||||
}
|
||||
|
||||
// Special handling for a few gates.
|
||||
specialFeatures = map[Feature]func(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool){
|
||||
allAlphaGate: setUnsetAlphaGates,
|
||||
}
|
||||
|
||||
// DefaultMutableFeatureGate is a mutable version of DefaultFeatureGate.
|
||||
// Only top-level commands/options setup and the k8s.io/apiserver/pkg/util/feature/testing package should make use of this.
|
||||
// Only top-level commands/options setup and the k8s.io/component-base/featuregate/testing package should make use of this.
|
||||
// Tests that need to modify feature gates for the duration of their test should use:
|
||||
// defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.<FeatureName>, <value>)()
|
||||
DefaultMutableFeatureGate MutableFeatureGate = NewFeatureGate()
|
||||
// defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.<FeatureName>, <value>)()
|
||||
DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
|
||||
|
||||
// DefaultFeatureGate is a shared global FeatureGate.
|
||||
// Top-level commands/options setup that needs to modify this feature gate should use DefaultMutableFeatureGate.
|
||||
DefaultFeatureGate FeatureGate = DefaultMutableFeatureGate
|
||||
DefaultFeatureGate featuregate.FeatureGate = DefaultMutableFeatureGate
|
||||
)
|
||||
|
||||
type FeatureSpec struct {
|
||||
// Default is the default enablement state for the feature
|
||||
Default bool
|
||||
// LockToDefault indicates that the feature is locked to its default and cannot be changed
|
||||
LockToDefault bool
|
||||
// PreRelease indicates the maturity level of the feature
|
||||
PreRelease prerelease
|
||||
}
|
||||
|
||||
type prerelease string
|
||||
|
||||
const (
|
||||
// Values for PreRelease.
|
||||
Alpha = prerelease("ALPHA")
|
||||
Beta = prerelease("BETA")
|
||||
GA = prerelease("")
|
||||
|
||||
// Deprecated
|
||||
Deprecated = prerelease("DEPRECATED")
|
||||
)
|
||||
|
||||
// FeatureGate indicates whether a given feature is enabled or not
|
||||
type FeatureGate interface {
|
||||
// Enabled returns true if the key is enabled.
|
||||
Enabled(key Feature) bool
|
||||
// KnownFeatures returns a slice of strings describing the FeatureGate's known features.
|
||||
KnownFeatures() []string
|
||||
// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be
|
||||
// set on the copy without mutating the original. This is useful for validating
|
||||
// config against potential feature gate changes before committing those changes.
|
||||
DeepCopy() MutableFeatureGate
|
||||
}
|
||||
|
||||
// MutableFeatureGate parses and stores flag gates for known features from
|
||||
// a string like feature1=true,feature2=false,...
|
||||
type MutableFeatureGate interface {
|
||||
FeatureGate
|
||||
|
||||
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
|
||||
AddFlag(fs *pflag.FlagSet)
|
||||
// Set parses and stores flag gates for known features
|
||||
// from a string like feature1=true,feature2=false,...
|
||||
Set(value string) error
|
||||
// SetFromMap stores flag gates for known features from a map[string]bool or returns an error
|
||||
SetFromMap(m map[string]bool) error
|
||||
// Add adds features to the featureGate.
|
||||
Add(features map[Feature]FeatureSpec) error
|
||||
}
|
||||
|
||||
// featureGate implements FeatureGate as well as pflag.Value for flag parsing.
|
||||
type featureGate struct {
|
||||
special map[Feature]func(map[Feature]FeatureSpec, map[Feature]bool, bool)
|
||||
|
||||
// lock guards writes to known, enabled, and reads/writes of closed
|
||||
lock sync.Mutex
|
||||
// known holds a map[Feature]FeatureSpec
|
||||
known *atomic.Value
|
||||
// enabled holds a map[Feature]bool
|
||||
enabled *atomic.Value
|
||||
// closed is set to true when AddFlag is called, and prevents subsequent calls to Add
|
||||
closed bool
|
||||
}
|
||||
|
||||
func setUnsetAlphaGates(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool) {
|
||||
for k, v := range known {
|
||||
if v.PreRelease == Alpha {
|
||||
if _, found := enabled[k]; !found {
|
||||
enabled[k] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set, String, and Type implement pflag.Value
|
||||
var _ pflag.Value = &featureGate{}
|
||||
|
||||
func NewFeatureGate() *featureGate {
|
||||
known := map[Feature]FeatureSpec{}
|
||||
for k, v := range defaultFeatures {
|
||||
known[k] = v
|
||||
}
|
||||
|
||||
knownValue := &atomic.Value{}
|
||||
knownValue.Store(known)
|
||||
|
||||
enabled := map[Feature]bool{}
|
||||
enabledValue := &atomic.Value{}
|
||||
enabledValue.Store(enabled)
|
||||
|
||||
f := &featureGate{
|
||||
known: knownValue,
|
||||
special: specialFeatures,
|
||||
enabled: enabledValue,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Set parses a string of the form "key1=value1,key2=value2,..." into a
|
||||
// map[string]bool of known keys or returns an error.
|
||||
func (f *featureGate) Set(value string) error {
|
||||
m := make(map[string]bool)
|
||||
for _, s := range strings.Split(value, ",") {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
arr := strings.SplitN(s, "=", 2)
|
||||
k := strings.TrimSpace(arr[0])
|
||||
if len(arr) != 2 {
|
||||
return fmt.Errorf("missing bool value for %s", k)
|
||||
}
|
||||
v := strings.TrimSpace(arr[1])
|
||||
boolValue, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value of %s=%s, err: %v", k, v, err)
|
||||
}
|
||||
m[k] = boolValue
|
||||
}
|
||||
return f.SetFromMap(m)
|
||||
}
|
||||
|
||||
// SetFromMap stores flag gates for known features from a map[string]bool or returns an error
|
||||
func (f *featureGate) SetFromMap(m map[string]bool) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
// Copy existing state
|
||||
known := map[Feature]FeatureSpec{}
|
||||
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||
known[k] = v
|
||||
}
|
||||
enabled := map[Feature]bool{}
|
||||
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||
enabled[k] = v
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
k := Feature(k)
|
||||
featureSpec, ok := known[k]
|
||||
if !ok {
|
||||
return fmt.Errorf("unrecognized feature gate: %s", k)
|
||||
}
|
||||
if featureSpec.LockToDefault && featureSpec.Default != v {
|
||||
return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default)
|
||||
}
|
||||
enabled[k] = v
|
||||
// Handle "special" features like "all alpha gates"
|
||||
if fn, found := f.special[k]; found {
|
||||
fn(known, enabled, v)
|
||||
}
|
||||
|
||||
if featureSpec.PreRelease == Deprecated {
|
||||
klog.Warningf("Setting deprecated feature gate %s=%t. It will be removed in a future release.", k, v)
|
||||
} else if featureSpec.PreRelease == GA {
|
||||
klog.Warningf("Setting GA feature gate %s=%t. It will be removed in a future release.", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Persist changes
|
||||
f.known.Store(known)
|
||||
f.enabled.Store(enabled)
|
||||
|
||||
klog.V(1).Infof("feature gates: %v", f.enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...".
|
||||
func (f *featureGate) String() string {
|
||||
pairs := []string{}
|
||||
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
func (f *featureGate) Type() string {
|
||||
return "mapStringBool"
|
||||
}
|
||||
|
||||
// Add adds features to the featureGate.
|
||||
func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.closed {
|
||||
return fmt.Errorf("cannot add a feature gate after adding it to the flag set")
|
||||
}
|
||||
|
||||
// Copy existing state
|
||||
known := map[Feature]FeatureSpec{}
|
||||
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||
known[k] = v
|
||||
}
|
||||
|
||||
for name, spec := range features {
|
||||
if existingSpec, found := known[name]; found {
|
||||
if existingSpec == spec {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
|
||||
}
|
||||
|
||||
known[name] = spec
|
||||
}
|
||||
|
||||
// Persist updated state
|
||||
f.known.Store(known)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enabled returns true if the key is enabled.
|
||||
func (f *featureGate) Enabled(key Feature) bool {
|
||||
if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok {
|
||||
return v
|
||||
}
|
||||
return f.known.Load().(map[Feature]FeatureSpec)[key].Default
|
||||
}
|
||||
|
||||
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
|
||||
func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
|
||||
f.lock.Lock()
|
||||
// TODO(mtaufen): Shouldn't we just close it on the first Set/SetFromMap instead?
|
||||
// Not all components expose a feature gates flag using this AddFlag method, and
|
||||
// in the future, all components will completely stop exposing a feature gates flag,
|
||||
// in favor of componentconfig.
|
||||
f.closed = true
|
||||
f.lock.Unlock()
|
||||
|
||||
known := f.KnownFeatures()
|
||||
fs.Var(f, flagName, ""+
|
||||
"A set of key=value pairs that describe feature gates for alpha/experimental features. "+
|
||||
"Options are:\n"+strings.Join(known, "\n"))
|
||||
}
|
||||
|
||||
// KnownFeatures returns a slice of strings describing the FeatureGate's known features.
|
||||
// Deprecated and GA features are hidden from the list.
|
||||
func (f *featureGate) KnownFeatures() []string {
|
||||
var known []string
|
||||
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||
if v.PreRelease == GA || v.PreRelease == Deprecated {
|
||||
continue
|
||||
}
|
||||
known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.PreRelease, v.Default))
|
||||
}
|
||||
sort.Strings(known)
|
||||
return known
|
||||
}
|
||||
|
||||
// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be
|
||||
// set on the copy without mutating the original. This is useful for validating
|
||||
// config against potential feature gate changes before committing those changes.
|
||||
func (f *featureGate) DeepCopy() MutableFeatureGate {
|
||||
// Copy existing state.
|
||||
known := map[Feature]FeatureSpec{}
|
||||
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||
known[k] = v
|
||||
}
|
||||
enabled := map[Feature]bool{}
|
||||
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||
enabled[k] = v
|
||||
}
|
||||
|
||||
// Store copied state in new atomics.
|
||||
knownValue := &atomic.Value{}
|
||||
knownValue.Store(known)
|
||||
enabledValue := &atomic.Value{}
|
||||
enabledValue.Store(enabled)
|
||||
|
||||
// Construct a new featureGate around the copied state.
|
||||
// Note that specialFeatures is treated as immutable by convention,
|
||||
// and we maintain the value of f.closed across the copy.
|
||||
return &featureGate{
|
||||
special: specialFeatures,
|
||||
known: knownValue,
|
||||
enabled: enabledValue,
|
||||
closed: f.closed,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
vendor/k8s.io/apiserver/pkg/util/webhook/authentication.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/util/webhook/authentication.go
generated
vendored
|
|
@ -171,6 +171,7 @@ func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Conf
|
|||
// blindly overwrite existing values based on precedence
|
||||
if len(configAuthInfo.Token) > 0 {
|
||||
config.BearerToken = configAuthInfo.Token
|
||||
config.BearerTokenFile = configAuthInfo.TokenFile
|
||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
||||
if err != nil {
|
||||
|
|
@ -196,6 +197,9 @@ func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Conf
|
|||
config.Username = configAuthInfo.Username
|
||||
config.Password = configAuthInfo.Password
|
||||
}
|
||||
if configAuthInfo.Exec != nil {
|
||||
config.ExecProvider = configAuthInfo.Exec.DeepCopy()
|
||||
}
|
||||
if configAuthInfo.AuthProvider != nil {
|
||||
return nil, fmt.Errorf("auth provider not supported")
|
||||
}
|
||||
|
|
|
|||
7
vendor/k8s.io/apiserver/pkg/util/webhook/client.go
generated
vendored
7
vendor/k8s.io/apiserver/pkg/util/webhook/client.go
generated
vendored
|
|
@ -49,6 +49,7 @@ type ClientConfigService struct {
|
|||
Name string
|
||||
Namespace string
|
||||
Path string
|
||||
Port int32
|
||||
}
|
||||
|
||||
// ClientManager builds REST clients to talk to webhooks. It caches the clients
|
||||
|
|
@ -164,7 +165,11 @@ func (cm *ClientManager) HookClient(cc ClientConfig) (*rest.RESTClient, error) {
|
|||
}
|
||||
cfg.Dial = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if addr == host {
|
||||
u, err := cm.serviceResolver.ResolveEndpoint(cc.Service.Namespace, cc.Service.Name)
|
||||
port := cc.Service.Port
|
||||
if port == 0 {
|
||||
port = 443
|
||||
}
|
||||
u, err := cm.serviceResolver.ResolveEndpoint(cc.Service.Namespace, cc.Service.Name, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
13
vendor/k8s.io/apiserver/pkg/util/webhook/serviceresolver.go
generated
vendored
13
vendor/k8s.io/apiserver/pkg/util/webhook/serviceresolver.go
generated
vendored
|
|
@ -24,7 +24,7 @@ import (
|
|||
|
||||
// ServiceResolver knows how to convert a service reference into an actual location.
|
||||
type ServiceResolver interface {
|
||||
ResolveEndpoint(namespace, name string) (*url.URL, error)
|
||||
ResolveEndpoint(namespace, name string, port int32) (*url.URL, error)
|
||||
}
|
||||
|
||||
type defaultServiceResolver struct{}
|
||||
|
|
@ -35,12 +35,13 @@ func NewDefaultServiceResolver() ServiceResolver {
|
|||
}
|
||||
|
||||
// ResolveEndpoint constructs a service URL from a given namespace and name
|
||||
// note that the name and namespace are required and by default all created addresses use HTTPS scheme.
|
||||
// note that the name, namespace, and port are required and by default all
|
||||
// created addresses use HTTPS scheme.
|
||||
// for example:
|
||||
// name=ross namespace=andromeda resolves to https://ross.andromeda.svc:443
|
||||
func (sr defaultServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
|
||||
if len(name) == 0 || len(namespace) == 0 {
|
||||
return nil, errors.New("cannot resolve an empty service name or namespace")
|
||||
func (sr defaultServiceResolver) ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) {
|
||||
if len(name) == 0 || len(namespace) == 0 || port == 0 {
|
||||
return nil, errors.New("cannot resolve an empty service name or namespace or port")
|
||||
}
|
||||
return &url.URL{Scheme: "https", Host: fmt.Sprintf("%s.%s.svc:443", name, namespace)}, nil
|
||||
return &url.URL{Scheme: "https", Host: fmt.Sprintf("%s.%s.svc:%d", name, namespace, port)}, nil
|
||||
}
|
||||
|
|
|
|||
6
vendor/k8s.io/apiserver/pkg/util/webhook/validation.go
generated
vendored
6
vendor/k8s.io/apiserver/pkg/util/webhook/validation.go
generated
vendored
|
|
@ -51,7 +51,7 @@ func ValidateWebhookURL(fldPath *field.Path, URL string, forceHttps bool) field.
|
|||
return allErrors
|
||||
}
|
||||
|
||||
func ValidateWebhookService(fldPath *field.Path, namespace, name string, path *string) field.ErrorList {
|
||||
func ValidateWebhookService(fldPath *field.Path, namespace, name string, path *string, port int32) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
|
||||
if len(name) == 0 {
|
||||
|
|
@ -62,6 +62,10 @@ func ValidateWebhookService(fldPath *field.Path, namespace, name string, path *s
|
|||
allErrors = append(allErrors, field.Required(fldPath.Child("namespace"), "service namespace is required"))
|
||||
}
|
||||
|
||||
if errs := validation.IsValidPortNum(int(port)); errs != nil {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("port"), port, "port is not valid: "+strings.Join(errs, ", ")))
|
||||
}
|
||||
|
||||
if path == nil {
|
||||
return allErrors
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue