133
vendor/k8s.io/apiserver/pkg/endpoints/deprecation/deprecation.go
generated
vendored
Normal file
133
vendor/k8s.io/apiserver/pkg/endpoints/deprecation/deprecation.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deprecation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
type apiLifecycleDeprecated interface {
|
||||
APILifecycleDeprecated() (major, minor int)
|
||||
}
|
||||
|
||||
type apiLifecycleRemoved interface {
|
||||
APILifecycleRemoved() (major, minor int)
|
||||
}
|
||||
|
||||
type apiLifecycleReplacement interface {
|
||||
APILifecycleReplacement() schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// extract all digits at the beginning of the string
|
||||
var leadingDigits = regexp.MustCompile(`^(\d+)`)
|
||||
|
||||
// MajorMinor parses a numeric major/minor version from the provided version info.
|
||||
// The minor version drops all characters after the first non-digit character:
|
||||
// version.Info{Major:"1", Minor:"2+"} -> 1,2
|
||||
// version.Info{Major:"1", Minor:"2.3-build4"} -> 1,2
|
||||
func MajorMinor(v version.Info) (int, int, error) {
|
||||
major, err := strconv.Atoi(v.Major)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
minor, err := strconv.Atoi(leadingDigits.FindString(v.Minor))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return major, minor, nil
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if obj implements APILifecycleDeprecated() and returns
|
||||
// a major/minor version that is non-zero and is <= the specified current major/minor version.
|
||||
func IsDeprecated(obj runtime.Object, currentMajor, currentMinor int) bool {
|
||||
deprecated, isDeprecated := obj.(apiLifecycleDeprecated)
|
||||
if !isDeprecated {
|
||||
return false
|
||||
}
|
||||
|
||||
deprecatedMajor, deprecatedMinor := deprecated.APILifecycleDeprecated()
|
||||
// no deprecation version expressed
|
||||
if deprecatedMajor == 0 && deprecatedMinor == 0 {
|
||||
return false
|
||||
}
|
||||
// no current version info available
|
||||
if currentMajor == 0 && currentMinor == 0 {
|
||||
return true
|
||||
}
|
||||
// compare deprecation version to current version
|
||||
if deprecatedMajor > currentMajor {
|
||||
return false
|
||||
}
|
||||
if deprecatedMajor == currentMajor && deprecatedMinor > currentMinor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// RemovedRelease returns the major/minor version in which the given object is unavailable (in the form "<major>.<minor>")
|
||||
// if the object implements APILifecycleRemoved() to indicate a non-zero removal version, and returns an empty string otherwise.
|
||||
func RemovedRelease(obj runtime.Object) string {
|
||||
if removed, hasRemovalInfo := obj.(apiLifecycleRemoved); hasRemovalInfo {
|
||||
removedMajor, removedMinor := removed.APILifecycleRemoved()
|
||||
if removedMajor != 0 || removedMinor != 0 {
|
||||
return fmt.Sprintf("%d.%d", removedMajor, removedMinor)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WarningMessage returns a human-readable deprecation warning if the object implements APILifecycleDeprecated()
|
||||
// to indicate a non-zero deprecated major/minor version and has a populated GetObjectKind().GroupVersionKind().
|
||||
func WarningMessage(obj runtime.Object) string {
|
||||
deprecated, isDeprecated := obj.(apiLifecycleDeprecated)
|
||||
if !isDeprecated {
|
||||
return ""
|
||||
}
|
||||
|
||||
deprecatedMajor, deprecatedMinor := deprecated.APILifecycleDeprecated()
|
||||
if deprecatedMajor == 0 && deprecatedMinor == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
if gvk.Empty() {
|
||||
return ""
|
||||
}
|
||||
deprecationWarning := fmt.Sprintf("%s %s is deprecated in v%d.%d+", gvk.GroupVersion().String(), gvk.Kind, deprecatedMajor, deprecatedMinor)
|
||||
|
||||
if removed, hasRemovalInfo := obj.(apiLifecycleRemoved); hasRemovalInfo {
|
||||
removedMajor, removedMinor := removed.APILifecycleRemoved()
|
||||
if removedMajor != 0 || removedMinor != 0 {
|
||||
deprecationWarning = deprecationWarning + fmt.Sprintf(", unavailable in v%d.%d+", removedMajor, removedMinor)
|
||||
}
|
||||
}
|
||||
|
||||
if replaced, hasReplacement := obj.(apiLifecycleReplacement); hasReplacement {
|
||||
replacement := replaced.APILifecycleReplacement()
|
||||
if !replacement.Empty() {
|
||||
deprecationWarning = deprecationWarning + fmt.Sprintf("; use %s %s", replacement.GroupVersion().String(), replacement.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
return deprecationWarning
|
||||
}
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/util.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/discovery/util.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const APIGroupPrefix = "/apis"
|
||||
|
||||
96
vendor/k8s.io/apiserver/pkg/endpoints/filterlatency/filterlatency.go
generated
vendored
Normal file
96
vendor/k8s.io/apiserver/pkg/endpoints/filterlatency/filterlatency.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package filterlatency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
utilclock "k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
type requestFilterRecordKeyType int
|
||||
|
||||
// requestFilterRecordKey is the context key for a request filter record struct.
|
||||
const requestFilterRecordKey requestFilterRecordKeyType = iota
|
||||
|
||||
type requestFilterRecord struct {
|
||||
name string
|
||||
startedTimestamp time.Time
|
||||
}
|
||||
|
||||
// withRequestFilterRecord attaches the given request filter record to the parent context.
|
||||
func withRequestFilterRecord(parent context.Context, fr *requestFilterRecord) context.Context {
|
||||
return apirequest.WithValue(parent, requestFilterRecordKey, fr)
|
||||
}
|
||||
|
||||
// requestFilterRecordFrom returns the request filter record from the given context.
|
||||
func requestFilterRecordFrom(ctx context.Context) *requestFilterRecord {
|
||||
fr, _ := ctx.Value(requestFilterRecordKey).(*requestFilterRecord)
|
||||
return fr
|
||||
}
|
||||
|
||||
// TrackStarted measures the timestamp the given handler has started execution
|
||||
// by attaching a handler to the chain.
|
||||
func TrackStarted(handler http.Handler, name string) http.Handler {
|
||||
return trackStarted(handler, name, utilclock.RealClock{})
|
||||
}
|
||||
|
||||
// TrackCompleted measures the timestamp the given handler has completed execution and then
|
||||
// it updates the corresponding metric with the filter latency duration.
|
||||
func TrackCompleted(handler http.Handler) http.Handler {
|
||||
return trackCompleted(handler, utilclock.RealClock{}, func(ctx context.Context, fr *requestFilterRecord, completedAt time.Time) {
|
||||
metrics.RecordFilterLatency(ctx, fr.name, completedAt.Sub(fr.startedTimestamp))
|
||||
})
|
||||
}
|
||||
|
||||
func trackStarted(handler http.Handler, name string, clock utilclock.PassiveClock) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if fr := requestFilterRecordFrom(ctx); fr != nil {
|
||||
fr.name = name
|
||||
fr.startedTimestamp = clock.Now()
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
fr := &requestFilterRecord{
|
||||
name: name,
|
||||
startedTimestamp: clock.Now(),
|
||||
}
|
||||
r = r.WithContext(withRequestFilterRecord(ctx, fr))
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func trackCompleted(handler http.Handler, clock utilclock.PassiveClock, action func(context.Context, *requestFilterRecord, time.Time)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// The previous filter has just completed.
|
||||
completedAt := clock.Now()
|
||||
|
||||
defer handler.ServeHTTP(w, r)
|
||||
|
||||
ctx := r.Context()
|
||||
if fr := requestFilterRecordFrom(ctx); fr != nil {
|
||||
action(ctx, fr, completedAt)
|
||||
}
|
||||
})
|
||||
}
|
||||
31
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
generated
vendored
31
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
generated
vendored
@@ -18,6 +18,7 @@ package filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -56,8 +57,8 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker, lon
|
||||
}
|
||||
|
||||
ev.Stage = auditinternal.StageRequestReceived
|
||||
if processed := processAuditEvent(sink, ev, omitStages); !processed {
|
||||
audit.ApiserverAuditDroppedCounter.Inc()
|
||||
if processed := processAuditEvent(ctx, sink, ev, omitStages); !processed {
|
||||
audit.ApiserverAuditDroppedCounter.WithContext(ctx).Inc()
|
||||
responsewriters.InternalError(w, req, errors.New("failed to store audit event"))
|
||||
return
|
||||
}
|
||||
@@ -70,7 +71,7 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker, lon
|
||||
longRunningSink = sink
|
||||
}
|
||||
}
|
||||
respWriter := decorateResponseWriter(w, ev, longRunningSink, omitStages)
|
||||
respWriter := decorateResponseWriter(ctx, w, ev, longRunningSink, omitStages)
|
||||
|
||||
// send audit event when we leave this func, either via a panic or cleanly. In the case of long
|
||||
// running requests, this will be the second audit event.
|
||||
@@ -84,7 +85,7 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker, lon
|
||||
Reason: metav1.StatusReasonInternalError,
|
||||
Message: fmt.Sprintf("APIServer panic'd: %v", r),
|
||||
}
|
||||
processAuditEvent(sink, ev, omitStages)
|
||||
processAuditEvent(ctx, sink, ev, omitStages)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -98,14 +99,14 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker, lon
|
||||
if ev.ResponseStatus == nil && longRunningSink != nil {
|
||||
ev.ResponseStatus = fakedSuccessStatus
|
||||
ev.Stage = auditinternal.StageResponseStarted
|
||||
processAuditEvent(longRunningSink, ev, omitStages)
|
||||
processAuditEvent(ctx, longRunningSink, ev, omitStages)
|
||||
}
|
||||
|
||||
ev.Stage = auditinternal.StageResponseComplete
|
||||
if ev.ResponseStatus == nil {
|
||||
ev.ResponseStatus = fakedSuccessStatus
|
||||
}
|
||||
processAuditEvent(sink, ev, omitStages)
|
||||
processAuditEvent(ctx, sink, ev, omitStages)
|
||||
}()
|
||||
handler.ServeHTTP(respWriter, req)
|
||||
})
|
||||
@@ -125,13 +126,17 @@ func createAuditEventAndAttachToContext(req *http.Request, policy policy.Checker
|
||||
}
|
||||
|
||||
level, omitStages := policy.LevelAndStages(attribs)
|
||||
audit.ObservePolicyLevel(level)
|
||||
audit.ObservePolicyLevel(ctx, level)
|
||||
if level == auditinternal.LevelNone {
|
||||
// Don't audit.
|
||||
return req, nil, nil, nil
|
||||
}
|
||||
|
||||
ev, err := audit.NewEventFromRequest(req, level, attribs)
|
||||
requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(ctx)
|
||||
if !ok {
|
||||
requestReceivedTimestamp = time.Now()
|
||||
}
|
||||
ev, err := audit.NewEventFromRequest(req, requestReceivedTimestamp, level, attribs)
|
||||
if err != nil {
|
||||
return req, nil, nil, fmt.Errorf("failed to complete audit event from request: %v", err)
|
||||
}
|
||||
@@ -141,7 +146,7 @@ func createAuditEventAndAttachToContext(req *http.Request, policy policy.Checker
|
||||
return req, ev, omitStages, nil
|
||||
}
|
||||
|
||||
func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) bool {
|
||||
func processAuditEvent(ctx context.Context, sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) bool {
|
||||
for _, stage := range omitStages {
|
||||
if ev.Stage == stage {
|
||||
return true
|
||||
@@ -153,12 +158,13 @@ func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []au
|
||||
} else {
|
||||
ev.StageTimestamp = metav1.NewMicroTime(time.Now())
|
||||
}
|
||||
audit.ObserveEvent()
|
||||
audit.ObserveEvent(ctx)
|
||||
return sink.ProcessEvents(ev)
|
||||
}
|
||||
|
||||
func decorateResponseWriter(responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter {
|
||||
func decorateResponseWriter(ctx context.Context, responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter {
|
||||
delegate := &auditResponseWriter{
|
||||
ctx: ctx,
|
||||
ResponseWriter: responseWriter,
|
||||
event: ev,
|
||||
sink: sink,
|
||||
@@ -182,6 +188,7 @@ var _ http.ResponseWriter = &auditResponseWriter{}
|
||||
// create immediately an event (for long running requests).
|
||||
type auditResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
ctx context.Context
|
||||
event *auditinternal.Event
|
||||
once sync.Once
|
||||
sink audit.Sink
|
||||
@@ -201,7 +208,7 @@ func (a *auditResponseWriter) processCode(code int) {
|
||||
a.event.Stage = auditinternal.StageResponseStarted
|
||||
|
||||
if a.sink != nil {
|
||||
processAuditEvent(a.sink, a.event, a.omitStages)
|
||||
processAuditEvent(a.ctx, a.sink, a.event, a.omitStages)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
39
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit_annotations.go
generated
vendored
Normal file
39
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit_annotations.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/audit/policy"
|
||||
)
|
||||
|
||||
// WithAuditAnnotations decorates a http.Handler with a []{key, value} that is merged
|
||||
// with the audit.Event.Annotations map. This allows layers that run before WithAudit
|
||||
// (such as authentication) to assert annotations.
|
||||
// If sink or audit policy is nil, no decoration takes place.
|
||||
func WithAuditAnnotations(handler http.Handler, sink audit.Sink, policy policy.Checker) http.Handler {
|
||||
// no need to wrap if auditing is disabled
|
||||
if sink == nil || policy == nil {
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req = req.WithContext(audit.WithAuditAnnotations(req.Context()))
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
23
vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
generated
vendored
23
vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
generated
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package filters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -28,16 +29,22 @@ import (
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type recordMetrics func(context.Context, *authenticator.Response, bool, error, authenticator.Audiences, time.Time, time.Time)
|
||||
|
||||
// WithAuthentication creates an http handler that tries to authenticate the given request as a user, and then
|
||||
// stores any such user found onto the provided context for the request. If authentication fails or returns an error
|
||||
// the failed handler is used. On success, "Authorization" header is removed from the request and handler
|
||||
// is invoked to serve the request.
|
||||
func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences) http.Handler {
|
||||
return withAuthentication(handler, auth, failed, apiAuds, recordAuthMetrics)
|
||||
}
|
||||
|
||||
func withAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences, metrics recordMetrics) http.Handler {
|
||||
if auth == nil {
|
||||
klog.Warningf("Authentication is disabled")
|
||||
klog.Warning("Authentication is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -47,10 +54,13 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request, failed
|
||||
req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
|
||||
}
|
||||
resp, ok, err := auth.AuthenticateRequest(req)
|
||||
defer recordAuthMetrics(resp, ok, err, apiAuds, authenticationStart)
|
||||
authenticationFinish := time.Now()
|
||||
defer func() {
|
||||
metrics(req.Context(), resp, ok, err, apiAuds, authenticationStart, authenticationFinish)
|
||||
}()
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
klog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
||||
klog.ErrorS(err, "Unable to authenticate the request")
|
||||
}
|
||||
failed.ServeHTTP(w, req)
|
||||
return
|
||||
@@ -71,11 +81,8 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request, failed
|
||||
})
|
||||
}
|
||||
|
||||
func Unauthorized(s runtime.NegotiatedSerializer, supportsBasicAuth bool) http.Handler {
|
||||
func Unauthorized(s runtime.NegotiatedSerializer) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if supportsBasicAuth {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="kubernetes-master"`)
|
||||
}
|
||||
ctx := req.Context()
|
||||
requestInfo, found := genericapirequest.RequestInfoFrom(ctx)
|
||||
if !found {
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go
generated
vendored
@@ -52,7 +52,7 @@ func WithFailedAuthenticationAudit(failedHandler http.Handler, sink audit.Sink,
|
||||
ev.ResponseStatus.Message = getAuthMethods(req)
|
||||
ev.Stage = auditinternal.StageResponseStarted
|
||||
|
||||
rw := decorateResponseWriter(w, ev, sink, omitStages)
|
||||
rw := decorateResponseWriter(req.Context(), w, ev, sink, omitStages)
|
||||
failedHandler.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
6
vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
generated
vendored
6
vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
generated
vendored
@@ -21,7 +21,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
@@ -44,7 +44,7 @@ const (
|
||||
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
|
||||
func WithAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
|
||||
if a == nil {
|
||||
klog.Warningf("Authorization is disabled")
|
||||
klog.Warning("Authorization is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -70,7 +70,7 @@ func WithAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.
|
||||
return
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
|
||||
klog.V(4).InfoS("Forbidden", "URI", req.RequestURI, "Reason", reason)
|
||||
audit.LogAnnotation(ae, decisionAnnotationKey, decisionForbid)
|
||||
audit.LogAnnotation(ae, reasonAnnotationKey, reason)
|
||||
responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
|
||||
|
||||
41
vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
generated
vendored
41
vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
@@ -104,23 +104,50 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.
|
||||
userExtra[extraKey] = append(userExtra[extraKey], extraValue)
|
||||
|
||||
default:
|
||||
klog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest)
|
||||
klog.V(4).InfoS("unknown impersonation request type", "Request", impersonationRequest)
|
||||
responsewriters.Forbidden(ctx, actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest), s)
|
||||
return
|
||||
}
|
||||
|
||||
decision, reason, err := a.Authorize(ctx, actingAsAttributes)
|
||||
if err != nil || decision != authorizer.DecisionAllow {
|
||||
klog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err)
|
||||
klog.V(4).InfoS("Forbidden", "URI", req.RequestURI, "Reason", reason, "Error", err)
|
||||
responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !groupsSpecified && username != user.Anonymous {
|
||||
// When impersonating a non-anonymous user, if no groups were specified
|
||||
// include the system:authenticated group in the impersonated user info
|
||||
groups = append(groups, user.AllAuthenticated)
|
||||
if username != user.Anonymous {
|
||||
// When impersonating a non-anonymous user, include the 'system:authenticated' group
|
||||
// in the impersonated user info:
|
||||
// - if no groups were specified
|
||||
// - if a group has been specified other than 'system:authenticated'
|
||||
//
|
||||
// If 'system:unauthenticated' group has been specified we should not include
|
||||
// the 'system:authenticated' group.
|
||||
addAuthenticated := true
|
||||
for _, group := range groups {
|
||||
if group == user.AllAuthenticated || group == user.AllUnauthenticated {
|
||||
addAuthenticated = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if addAuthenticated {
|
||||
groups = append(groups, user.AllAuthenticated)
|
||||
}
|
||||
} else {
|
||||
addUnauthenticated := true
|
||||
for _, group := range groups {
|
||||
if group == user.AllUnauthenticated {
|
||||
addUnauthenticated = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if addUnauthenticated {
|
||||
groups = append(groups, user.AllUnauthenticated)
|
||||
}
|
||||
}
|
||||
|
||||
newUser := &user.DefaultInfo{
|
||||
|
||||
11
vendor/k8s.io/apiserver/pkg/endpoints/filters/metrics.go
generated
vendored
11
vendor/k8s.io/apiserver/pkg/endpoints/filters/metrics.go
generated
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package filters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -27,7 +28,7 @@ import (
|
||||
|
||||
/*
|
||||
* By default, all the following metrics are defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
@@ -75,7 +76,7 @@ func init() {
|
||||
legacyregistry.MustRegister(authenticationLatency)
|
||||
}
|
||||
|
||||
func recordAuthMetrics(resp *authenticator.Response, ok bool, err error, apiAudiences authenticator.Audiences, authStart time.Time) {
|
||||
func recordAuthMetrics(ctx context.Context, resp *authenticator.Response, ok bool, err error, apiAudiences authenticator.Audiences, authStart time.Time, authFinish time.Time) {
|
||||
var resultLabel string
|
||||
|
||||
switch {
|
||||
@@ -85,11 +86,11 @@ func recordAuthMetrics(resp *authenticator.Response, ok bool, err error, apiAudi
|
||||
resultLabel = failureLabel
|
||||
default:
|
||||
resultLabel = successLabel
|
||||
authenticatedUserCounter.WithLabelValues(compressUsername(resp.User.GetName())).Inc()
|
||||
authenticatedUserCounter.WithContext(ctx).WithLabelValues(compressUsername(resp.User.GetName())).Inc()
|
||||
}
|
||||
|
||||
authenticatedAttemptsCounter.WithLabelValues(resultLabel).Inc()
|
||||
authenticationLatency.WithLabelValues(resultLabel).Observe(time.Since(authStart).Seconds())
|
||||
authenticatedAttemptsCounter.WithContext(ctx).WithLabelValues(resultLabel).Inc()
|
||||
authenticationLatency.WithContext(ctx).WithLabelValues(resultLabel).Observe(authFinish.Sub(authStart).Seconds())
|
||||
}
|
||||
|
||||
// compressUsername maps all possible usernames onto a small set of categories
|
||||
|
||||
172
vendor/k8s.io/apiserver/pkg/endpoints/filters/request_deadline.go
generated
vendored
Normal file
172
vendor/k8s.io/apiserver/pkg/endpoints/filters/request_deadline.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilclock "k8s.io/apimachinery/pkg/util/clock"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/audit/policy"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// The 'timeout' query parameter in the request URL has an invalid duration specifier
|
||||
invalidTimeoutInURL = "invalid timeout specified in the request URL"
|
||||
)
|
||||
|
||||
// WithRequestDeadline determines the timeout duration applicable to the given request and sets a new context
|
||||
// with the appropriate deadline.
|
||||
// auditWrapper provides an http.Handler that audits a failed request.
|
||||
// longRunning returns true if he given request is a long running request.
|
||||
// requestTimeoutMaximum specifies the default request timeout value.
|
||||
func WithRequestDeadline(handler http.Handler, sink audit.Sink, policy policy.Checker, longRunning request.LongRunningRequestCheck,
|
||||
negotiatedSerializer runtime.NegotiatedSerializer, requestTimeoutMaximum time.Duration) http.Handler {
|
||||
return withRequestDeadline(handler, sink, policy, longRunning, negotiatedSerializer, requestTimeoutMaximum, utilclock.RealClock{})
|
||||
}
|
||||
|
||||
func withRequestDeadline(handler http.Handler, sink audit.Sink, policy policy.Checker, longRunning request.LongRunningRequestCheck,
|
||||
negotiatedSerializer runtime.NegotiatedSerializer, requestTimeoutMaximum time.Duration, clock utilclock.PassiveClock) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
handleError(w, req, http.StatusInternalServerError, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong"))
|
||||
return
|
||||
}
|
||||
if longRunning(req, requestInfo) {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
userSpecifiedTimeout, ok, err := parseTimeout(req)
|
||||
if err != nil {
|
||||
statusErr := apierrors.NewBadRequest(err.Error())
|
||||
|
||||
klog.Errorf("Error - %s: %#v", err.Error(), req.RequestURI)
|
||||
|
||||
failed := failedErrorHandler(negotiatedSerializer, statusErr)
|
||||
failWithAudit := withFailedRequestAudit(failed, statusErr, sink, policy)
|
||||
failWithAudit.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
timeout := requestTimeoutMaximum
|
||||
if ok {
|
||||
// we use the default timeout enforced by the apiserver:
|
||||
// - if the user has specified a timeout of 0s, this implies no timeout on the user's part.
|
||||
// - if the user has specified a timeout that exceeds the maximum deadline allowed by the apiserver.
|
||||
if userSpecifiedTimeout > 0 && userSpecifiedTimeout < requestTimeoutMaximum {
|
||||
timeout = userSpecifiedTimeout
|
||||
}
|
||||
}
|
||||
|
||||
started := clock.Now()
|
||||
if requestStartedTimestamp, ok := request.ReceivedTimestampFrom(ctx); ok {
|
||||
started = requestStartedTimestamp
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithDeadline(ctx, started.Add(timeout))
|
||||
defer cancel()
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// withFailedRequestAudit decorates a failed http.Handler and is used to audit a failed request.
|
||||
// statusErr is used to populate the Message property of ResponseStatus.
|
||||
func withFailedRequestAudit(failedHandler http.Handler, statusErr *apierrors.StatusError, sink audit.Sink, policy policy.Checker) http.Handler {
|
||||
if sink == nil || policy == nil {
|
||||
return failedHandler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req, ev, omitStages, err := createAuditEventAndAttachToContext(req, policy)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
|
||||
responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
|
||||
return
|
||||
}
|
||||
if ev == nil {
|
||||
failedHandler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ev.ResponseStatus = &metav1.Status{}
|
||||
ev.Stage = auditinternal.StageResponseStarted
|
||||
if statusErr != nil {
|
||||
ev.ResponseStatus.Message = statusErr.Error()
|
||||
}
|
||||
|
||||
rw := decorateResponseWriter(req.Context(), w, ev, sink, omitStages)
|
||||
failedHandler.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// failedErrorHandler returns an http.Handler that uses the specified StatusError object
|
||||
// to render an error response to the request.
|
||||
func failedErrorHandler(s runtime.NegotiatedSerializer, statusError *apierrors.StatusError) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
requestInfo, found := request.RequestInfoFrom(ctx)
|
||||
if !found {
|
||||
responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context"))
|
||||
return
|
||||
}
|
||||
|
||||
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
|
||||
responsewriters.ErrorNegotiated(statusError, s, gv, w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// parseTimeout parses the given HTTP request URL and extracts the timeout query parameter
|
||||
// value if specified by the user.
|
||||
// If a timeout is not specified the function returns false and err is set to nil
|
||||
// If the value specified is malformed then the function returns false and err is set
|
||||
func parseTimeout(req *http.Request) (time.Duration, bool, error) {
|
||||
value := req.URL.Query().Get("timeout")
|
||||
if value == "" {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return 0, false, fmt.Errorf("%s - %s", invalidTimeoutInURL, err.Error())
|
||||
}
|
||||
|
||||
return timeout, true, nil
|
||||
}
|
||||
|
||||
func handleError(w http.ResponseWriter, r *http.Request, code int, err error) {
|
||||
errorMsg := fmt.Sprintf("Error - %s: %#v", err.Error(), r.RequestURI)
|
||||
http.Error(w, errorMsg, code)
|
||||
klog.Errorf(errorMsg)
|
||||
}
|
||||
40
vendor/k8s.io/apiserver/pkg/endpoints/filters/request_received_time.go
generated
vendored
Normal file
40
vendor/k8s.io/apiserver/pkg/endpoints/filters/request_received_time.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
utilclock "k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
// WithRequestReceivedTimestamp attaches the ReceivedTimestamp (the time the request reached
|
||||
// the apiserver) to the context.
|
||||
func WithRequestReceivedTimestamp(handler http.Handler) http.Handler {
|
||||
return withRequestReceivedTimestampWithClock(handler, utilclock.RealClock{})
|
||||
}
|
||||
|
||||
// The clock is passed as a parameter, handy for unit testing.
|
||||
func withRequestReceivedTimestampWithClock(handler http.Handler, clock utilclock.PassiveClock) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
req = req.WithContext(request.WithReceivedTimestamp(ctx, clock.Now()))
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
121
vendor/k8s.io/apiserver/pkg/endpoints/filters/storageversion.go
generated
vendored
Normal file
121
vendor/k8s.io/apiserver/pkg/endpoints/filters/storageversion.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/storageversion"
|
||||
_ "k8s.io/component-base/metrics/prometheus/workqueue" // for workqueue metric registration
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// WithStorageVersionPrecondition checks if the storage version barrier has
|
||||
// completed, if not, it only passes the following API requests:
|
||||
// 1. non-resource requests,
|
||||
// 2. read requests,
|
||||
// 3. write requests to the storageversion API,
|
||||
// 4. create requests to the namespace API sent by apiserver itself,
|
||||
// 5. write requests to the lease API in kube-system namespace,
|
||||
// 6. resources whose StorageVersion is not pending update, including non-persisted resources.
|
||||
func WithStorageVersionPrecondition(handler http.Handler, svm storageversion.Manager, s runtime.NegotiatedSerializer) http.Handler {
|
||||
if svm == nil {
|
||||
// TODO(roycaihw): switch to warning after the feature graduate to beta/GA
|
||||
klog.V(2).Infof("Storage Version barrier is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if svm.Completed() {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
ctx := req.Context()
|
||||
requestInfo, found := request.RequestInfoFrom(ctx)
|
||||
if !found {
|
||||
responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context"))
|
||||
return
|
||||
}
|
||||
// Allow non-resource requests
|
||||
if !requestInfo.IsResourceRequest {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
// Allow read requests
|
||||
if requestInfo.Verb == "get" || requestInfo.Verb == "list" || requestInfo.Verb == "watch" {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
// Allow writes to the storage version API
|
||||
if requestInfo.APIGroup == "internal.apiserver.k8s.io" && requestInfo.Resource == "storageversions" {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
// The system namespace is required for apiserver-identity lease to exist. Allow the apiserver
|
||||
// itself to create namespaces.
|
||||
// NOTE: with this exception, if the bootstrap client writes namespaces with a new version,
|
||||
// and the upgraded apiserver dies before updating the StorageVersion for namespaces, the
|
||||
// storage migrator won't be able to tell these namespaces are stored in a different version in etcd.
|
||||
// Because the bootstrap client only creates system namespace and doesn't update them, this can
|
||||
// only happen if the upgraded apiserver is the first apiserver that kicks off namespace creation,
|
||||
// or if an upgraded server that joins an existing cluster has new system namespaces (other
|
||||
// than kube-system, kube-public, kube-node-lease) that need to be created.
|
||||
u, hasUser := request.UserFrom(ctx)
|
||||
if requestInfo.APIGroup == "" && requestInfo.Resource == "namespaces" &&
|
||||
requestInfo.Verb == "create" && hasUser &&
|
||||
u.GetName() == user.APIServerUser && contains(u.GetGroups(), user.SystemPrivilegedGroup) {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
// Allow writes to the lease API in kube-system. The storage version API depends on the
|
||||
// apiserver-identity leases to operate. Leases in kube-system are either apiserver-identity
|
||||
// lease (which gets garbage collected when stale) or leader-election leases (which gets
|
||||
// periodically updated by system components). Both types of leases won't be stale in etcd.
|
||||
if requestInfo.APIGroup == "coordination.k8s.io" && requestInfo.Resource == "leases" &&
|
||||
requestInfo.Namespace == metav1.NamespaceSystem {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
// If the resource's StorageVersion is not in the to-be-updated list, let it pass.
|
||||
// Non-persisted resources are not in the to-be-updated list, so they will pass.
|
||||
gr := schema.GroupResource{requestInfo.APIGroup, requestInfo.Resource}
|
||||
if !svm.PendingUpdate(gr) {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
gv := schema.GroupVersion{requestInfo.APIGroup, requestInfo.APIVersion}
|
||||
responsewriters.ErrorNegotiated(apierrors.NewServiceUnavailable(fmt.Sprintf("wait for storage version registration to complete for resource: %v, last seen error: %v", gr, svm.LastUpdateError(gr))), s, gv, w, req)
|
||||
})
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
133
vendor/k8s.io/apiserver/pkg/endpoints/filters/warning.go
generated
vendored
Normal file
133
vendor/k8s.io/apiserver/pkg/endpoints/filters/warning.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
)
|
||||
|
||||
// WithWarningRecorder attaches a deduplicating k8s.io/apiserver/pkg/warning#WarningRecorder to the request context.
|
||||
func WithWarningRecorder(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
recorder := &recorder{writer: w}
|
||||
req = req.WithContext(warning.WithWarningRecorder(req.Context(), recorder))
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
truncateAtTotalRunes = 4 * 1024
|
||||
truncateItemRunes = 256
|
||||
)
|
||||
|
||||
type recordedWarning struct {
|
||||
agent string
|
||||
text string
|
||||
}
|
||||
|
||||
type recorder struct {
|
||||
// lock guards calls to AddWarning from multiple threads
|
||||
lock sync.Mutex
|
||||
|
||||
// recorded tracks whether AddWarning was already called with a given text
|
||||
recorded map[string]bool
|
||||
|
||||
// ordered tracks warnings added so they can be replayed and truncated if needed
|
||||
ordered []recordedWarning
|
||||
|
||||
// written tracks how many runes of text have been added as warning headers
|
||||
written int
|
||||
|
||||
// truncating tracks if we have already exceeded truncateAtTotalRunes and are now truncating warning messages as we add them
|
||||
truncating bool
|
||||
|
||||
// writer is the response writer to add warning headers to
|
||||
writer http.ResponseWriter
|
||||
}
|
||||
|
||||
func (r *recorder) AddWarning(agent, text string) {
|
||||
if len(text) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
// if we've already exceeded our limit and are already truncating, return early
|
||||
if r.written >= truncateAtTotalRunes && r.truncating {
|
||||
return
|
||||
}
|
||||
|
||||
// init if needed
|
||||
if r.recorded == nil {
|
||||
r.recorded = map[string]bool{}
|
||||
}
|
||||
|
||||
// dedupe if already warned
|
||||
if r.recorded[text] {
|
||||
return
|
||||
}
|
||||
r.recorded[text] = true
|
||||
r.ordered = append(r.ordered, recordedWarning{agent: agent, text: text})
|
||||
|
||||
// truncate on a rune boundary, if needed
|
||||
textRuneLength := utf8.RuneCountInString(text)
|
||||
if r.truncating && textRuneLength > truncateItemRunes {
|
||||
text = string([]rune(text)[:truncateItemRunes])
|
||||
textRuneLength = truncateItemRunes
|
||||
}
|
||||
|
||||
// compute the header
|
||||
header, err := net.NewWarningHeader(299, agent, text)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if this fits within our limit, or we're already truncating, write and return
|
||||
if r.written+textRuneLength <= truncateAtTotalRunes || r.truncating {
|
||||
r.written += textRuneLength
|
||||
r.writer.Header().Add("Warning", header)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, enable truncation, reset, and replay the existing items as truncated warnings
|
||||
r.truncating = true
|
||||
r.written = 0
|
||||
r.writer.Header().Del("Warning")
|
||||
utilruntime.HandleError(fmt.Errorf("exceeded max warning header size, truncating"))
|
||||
for _, w := range r.ordered {
|
||||
agent := w.agent
|
||||
text := w.text
|
||||
|
||||
textRuneLength := utf8.RuneCountInString(text)
|
||||
if textRuneLength > truncateItemRunes {
|
||||
text = string([]rune(text)[:truncateItemRunes])
|
||||
textRuneLength = truncateItemRunes
|
||||
}
|
||||
if header, err := net.NewWarningHeader(299, agent, text); err == nil {
|
||||
r.written += textRuneLength
|
||||
r.writer.Header().Add("Warning", header)
|
||||
}
|
||||
}
|
||||
}
|
||||
42
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
generated
vendored
42
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
generated
vendored
@@ -30,10 +30,19 @@ import (
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storageversion"
|
||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||
)
|
||||
|
||||
// ConvertabilityChecker indicates what versions a GroupKind is available in.
|
||||
type ConvertabilityChecker interface {
|
||||
// VersionsForGroupKind indicates what versions are available to convert a group kind. This determines
|
||||
// what our decoding abilities are.
|
||||
VersionsForGroupKind(gk schema.GroupKind) []schema.GroupVersion
|
||||
}
|
||||
|
||||
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
||||
// It handles URLs of the form:
|
||||
// /${storage_key}[/${object_name}]
|
||||
@@ -65,12 +74,14 @@ type APIGroupVersion struct {
|
||||
Serializer runtime.NegotiatedSerializer
|
||||
ParameterCodec runtime.ParameterCodec
|
||||
|
||||
Typer runtime.ObjectTyper
|
||||
Creater runtime.ObjectCreater
|
||||
Convertor runtime.ObjectConvertor
|
||||
Defaulter runtime.ObjectDefaulter
|
||||
Linker runtime.SelfLinker
|
||||
UnsafeConvertor runtime.ObjectConvertor
|
||||
Typer runtime.ObjectTyper
|
||||
Creater runtime.ObjectCreater
|
||||
Convertor runtime.ObjectConvertor
|
||||
ConvertabilityChecker ConvertabilityChecker
|
||||
Defaulter runtime.ObjectDefaulter
|
||||
Linker runtime.SelfLinker
|
||||
UnsafeConvertor runtime.ObjectConvertor
|
||||
TypeConverter fieldmanager.TypeConverter
|
||||
|
||||
EquivalentResourceRegistry runtime.EquivalentResourceRegistry
|
||||
|
||||
@@ -94,7 +105,7 @@ type APIGroupVersion struct {
|
||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
|
||||
// in a slash.
|
||||
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
||||
func (g *APIGroupVersion) InstallREST(container *restful.Container) ([]*storageversion.ResourceInfo, error) {
|
||||
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
|
||||
installer := &APIInstaller{
|
||||
group: g,
|
||||
@@ -102,11 +113,24 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
||||
minRequestTimeout: g.MinRequestTimeout,
|
||||
}
|
||||
|
||||
apiResources, ws, registrationErrors := installer.Install()
|
||||
apiResources, resourceInfos, ws, registrationErrors := installer.Install()
|
||||
versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, staticLister{apiResources})
|
||||
versionDiscoveryHandler.AddToWebService(ws)
|
||||
container.Add(ws)
|
||||
return utilerrors.NewAggregate(registrationErrors)
|
||||
return removeNonPersistedResources(resourceInfos), utilerrors.NewAggregate(registrationErrors)
|
||||
}
|
||||
|
||||
func removeNonPersistedResources(infos []*storageversion.ResourceInfo) []*storageversion.ResourceInfo {
|
||||
var filtered []*storageversion.ResourceInfo
|
||||
for _, info := range infos {
|
||||
// if EncodingVersion is empty, then the apiserver does not
|
||||
// need to register this resource via the storage version API,
|
||||
// thus we can remove it.
|
||||
if info != nil && len(info.EncodingVersion) > 0 {
|
||||
filtered = append(filtered, info)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// staticLister implements the APIResourceLister interface
|
||||
|
||||
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
@@ -35,6 +35,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
@@ -44,10 +45,12 @@ import (
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
var namespaceGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}
|
||||
|
||||
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", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}})
|
||||
trace := utiltrace.New("Create", traceFields(req)...)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -55,9 +58,6 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
if err != nil {
|
||||
if includeName {
|
||||
@@ -74,9 +74,10 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
|
||||
// timeout inside the parent context is lower than requestTimeoutUpperBound.
|
||||
ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound)
|
||||
defer cancel()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
@@ -128,17 +129,21 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
||||
}
|
||||
trace.Step("Conversion done")
|
||||
|
||||
// On create, get name from new object if unset
|
||||
if len(name) == 0 {
|
||||
_, name, _ = scope.Namer.ObjectName(obj)
|
||||
}
|
||||
if len(namespace) == 0 && *gvk == namespaceGVK {
|
||||
namespace = name
|
||||
}
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
|
||||
// On create, get name from new object if unset
|
||||
if len(name) == 0 {
|
||||
_, name, _ = scope.Namer.ObjectName(obj)
|
||||
}
|
||||
|
||||
trace.Step("About to store object in database")
|
||||
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
requestFunc := func() (runtime.Object, error) {
|
||||
@@ -150,19 +155,24 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
||||
options,
|
||||
)
|
||||
}
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
// Dedup owner references before updating managed fields
|
||||
dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
|
||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
||||
if scope.FieldManager != nil {
|
||||
liveObj, err := scope.Creater.New(scope.Kind)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err)
|
||||
}
|
||||
obj = scope.FieldManager.UpdateNoErrors(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
|
||||
admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
|
||||
}
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
|
||||
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Dedup owner references again after mutating admission happens
|
||||
dedupOwnerReferencesAndAddWarning(obj, req.Context(), true)
|
||||
result, err := requestFunc()
|
||||
// If the object wasn't committed to storage because it's serialized size was too large,
|
||||
// it is safe to remove managedFields (which can be large) and try again.
|
||||
|
||||
35
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
35
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
@@ -25,6 +25,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
|
||||
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -45,7 +46,7 @@ import (
|
||||
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", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}})
|
||||
trace := utiltrace.New("Delete", traceFields(req)...)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -53,16 +54,17 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
|
||||
// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
|
||||
// timeout inside the parent context is lower than requestTimeoutUpperBound.
|
||||
ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound)
|
||||
defer cancel()
|
||||
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
@@ -122,7 +124,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
|
||||
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) {
|
||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
||||
obj, deleted, err := r.Delete(ctx, name, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options)
|
||||
wasDeleted = deleted
|
||||
return obj, err
|
||||
@@ -140,7 +142,8 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
|
||||
// that will break existing clients.
|
||||
// Other cases where resource is not instantly deleted are: namespace deletion
|
||||
// and pod graceful deletion.
|
||||
if !wasDeleted && options.OrphanDependents != nil && *options.OrphanDependents == false {
|
||||
//lint:ignore SA1019 backwards compatibility
|
||||
if !wasDeleted && options.OrphanDependents != nil && !*options.OrphanDependents {
|
||||
status = http.StatusAccepted
|
||||
}
|
||||
// if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
|
||||
@@ -163,7 +166,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
|
||||
// DeleteCollection returns a function that will handle a collection deletion
|
||||
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", utiltrace.Field{"url", req.URL.Path})
|
||||
trace := utiltrace.New("Delete", traceFields(req)...)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -171,17 +174,17 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, err := scope.Namer.Namespace(req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
|
||||
// timeout inside the parent context is lower than requestTimeoutUpperBound.
|
||||
ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound)
|
||||
defer cancel()
|
||||
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
|
||||
@@ -198,6 +201,12 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
|
||||
return
|
||||
}
|
||||
|
||||
if errs := metainternalversionvalidation.ValidateListOptions(&listOptions); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// transform fields
|
||||
// TODO: DecodeParametersInto should do this.
|
||||
if listOptions.FieldSelector != nil {
|
||||
@@ -258,7 +267,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
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) {
|
||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
||||
return r.DeleteCollection(ctx, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options, &listOptions)
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
86
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go
generated
vendored
Normal file
86
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fieldmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
)
|
||||
|
||||
// InvalidManagedFieldsAfterMutatingAdmissionWarningFormat is the warning that a client receives
|
||||
// when a create/update/patch request results in invalid managedFields after going through the admission chain.
|
||||
const InvalidManagedFieldsAfterMutatingAdmissionWarningFormat = ".metadata.managedFields was in an invalid state after admission; this could be caused by an outdated mutating admission controller; please fix your requests: %v"
|
||||
|
||||
// NewManagedFieldsValidatingAdmissionController validates the managedFields after calling
|
||||
// the provided admission and resets them to their original state if they got changed to an invalid value
|
||||
func NewManagedFieldsValidatingAdmissionController(wrap admission.Interface) admission.Interface {
|
||||
if wrap == nil {
|
||||
return nil
|
||||
}
|
||||
return &managedFieldsValidatingAdmissionController{wrap: wrap}
|
||||
}
|
||||
|
||||
type managedFieldsValidatingAdmissionController struct {
|
||||
wrap admission.Interface
|
||||
}
|
||||
|
||||
var _ admission.Interface = &managedFieldsValidatingAdmissionController{}
|
||||
var _ admission.MutationInterface = &managedFieldsValidatingAdmissionController{}
|
||||
var _ admission.ValidationInterface = &managedFieldsValidatingAdmissionController{}
|
||||
|
||||
// Handles calls the wrapped admission.Interface if applicable
|
||||
func (admit *managedFieldsValidatingAdmissionController) Handles(operation admission.Operation) bool {
|
||||
return admit.wrap.Handles(operation)
|
||||
}
|
||||
|
||||
// Admit calls the wrapped admission.Interface if applicable and resets the managedFields to their state before admission if they
|
||||
// got modified in an invalid way
|
||||
func (admit *managedFieldsValidatingAdmissionController) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
|
||||
mutationInterface, isMutationInterface := admit.wrap.(admission.MutationInterface)
|
||||
if !isMutationInterface {
|
||||
return nil
|
||||
}
|
||||
objectMeta, err := meta.Accessor(a.GetObject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
managedFieldsBeforeAdmission := objectMeta.GetManagedFields()
|
||||
if err := mutationInterface.Admit(ctx, a, o); err != nil {
|
||||
return err
|
||||
}
|
||||
managedFieldsAfterAdmission := objectMeta.GetManagedFields()
|
||||
if _, err := DecodeManagedFields(managedFieldsAfterAdmission); err != nil {
|
||||
objectMeta.SetManagedFields(managedFieldsBeforeAdmission)
|
||||
warning.AddWarning(ctx, "",
|
||||
fmt.Sprintf(InvalidManagedFieldsAfterMutatingAdmissionWarningFormat,
|
||||
err.Error()),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate calls the wrapped admission.Interface if aplicable
|
||||
func (admit *managedFieldsValidatingAdmissionController) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
|
||||
if validationInterface, isValidationInterface := admit.wrap.(admission.ValidationInterface); isValidationInterface {
|
||||
return validationInterface.Validate(ctx, a, o)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/capmanagers.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/capmanagers.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
type capManagersManager struct {
|
||||
@@ -67,7 +67,7 @@ func (f *capManagersManager) capUpdateManagers(managed Managed) (newManaged Mana
|
||||
// Gather all entries from updates
|
||||
updaters := []string{}
|
||||
for manager, fields := range managed.Fields() {
|
||||
if fields.Applied() == false {
|
||||
if !fields.Applied() {
|
||||
updaters = append(updaters, manager)
|
||||
}
|
||||
}
|
||||
|
||||
115
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
115
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
@@ -26,9 +26,9 @@ 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/v3/fieldpath"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||
)
|
||||
|
||||
// DefaultMaxUpdateManagers defines the default maximum retained number of managedFields entries from updates
|
||||
@@ -66,73 +66,105 @@ type Manager interface {
|
||||
// FieldManager updates the managed fields and merge applied
|
||||
// configurations.
|
||||
type FieldManager struct {
|
||||
fieldManager Manager
|
||||
fieldManager Manager
|
||||
ignoreManagedFieldsFromRequestObject bool
|
||||
}
|
||||
|
||||
// NewFieldManager creates a new FieldManager that decodes, manages, then re-encodes managedFields
|
||||
// on update and apply requests.
|
||||
func NewFieldManager(f Manager) *FieldManager {
|
||||
return &FieldManager{f}
|
||||
func NewFieldManager(f Manager, ignoreManagedFieldsFromRequestObject bool) *FieldManager {
|
||||
return &FieldManager{fieldManager: f, ignoreManagedFieldsFromRequestObject: ignoreManagedFieldsFromRequestObject}
|
||||
}
|
||||
|
||||
// NewDefaultFieldManager creates a new FieldManager that merges apply requests
|
||||
// and update managed fields for other types of requests.
|
||||
func NewDefaultFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion) (*FieldManager, error) {
|
||||
f, err := NewStructuredMergeManager(models, objectConverter, objectDefaulter, kind.GroupVersion(), hub)
|
||||
func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, ignoreManagedFieldsFromRequestObject bool, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (*FieldManager, error) {
|
||||
f, err := NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||
}
|
||||
return newDefaultFieldManager(f, objectCreater, kind), nil
|
||||
return newDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, ignoreManagedFieldsFromRequestObject), nil
|
||||
}
|
||||
|
||||
// NewDefaultCRDFieldManager creates a new FieldManager specifically for
|
||||
// CRDs. This allows for the possibility of fields which are not defined
|
||||
// in models, as well as having no models defined at all.
|
||||
func NewDefaultCRDFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, preserveUnknownFields bool) (_ *FieldManager, err error) {
|
||||
f, err := NewCRDStructuredMergeManager(models, objectConverter, objectDefaulter, kind.GroupVersion(), hub, preserveUnknownFields)
|
||||
func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, ignoreManagedFieldsFromRequestObject bool, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ *FieldManager, err error) {
|
||||
f, err := NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||
}
|
||||
return newDefaultFieldManager(f, objectCreater, kind), nil
|
||||
return newDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, ignoreManagedFieldsFromRequestObject), nil
|
||||
}
|
||||
|
||||
// newDefaultFieldManager is a helper function which wraps a Manager with certain default logic.
|
||||
func newDefaultFieldManager(f Manager, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind) *FieldManager {
|
||||
func newDefaultFieldManager(f Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, ignoreManagedFieldsFromRequestObject bool) *FieldManager {
|
||||
f = NewStripMetaManager(f)
|
||||
f = NewManagedFieldsUpdater(f)
|
||||
f = NewBuildManagerInfoManager(f, kind.GroupVersion())
|
||||
f = NewCapManagersManager(f, DefaultMaxUpdateManagers)
|
||||
f = NewProbabilisticSkipNonAppliedManager(f, objectCreater, kind, DefaultTrackOnCreateProbability)
|
||||
return NewFieldManager(f)
|
||||
f = NewLastAppliedManager(f, typeConverter, objectConverter, kind.GroupVersion())
|
||||
f = NewLastAppliedUpdater(f)
|
||||
|
||||
return NewFieldManager(f, ignoreManagedFieldsFromRequestObject)
|
||||
}
|
||||
|
||||
// DecodeManagedFields converts ManagedFields from the wire format (api format)
|
||||
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||
func DecodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (Managed, error) {
|
||||
return internal.DecodeManagedFields(encodedManagedFields)
|
||||
}
|
||||
|
||||
func decodeLiveOrNew(liveObj, newObj runtime.Object, ignoreManagedFieldsFromRequestObject bool) (Managed, error) {
|
||||
liveAccessor, err := meta.Accessor(liveObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We take the managedFields of the live object in case the request tries to
|
||||
// manually set managedFields via a subresource.
|
||||
if ignoreManagedFieldsFromRequestObject {
|
||||
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
|
||||
}
|
||||
|
||||
// If the object doesn't have metadata, we should just return without trying to
|
||||
// set the managedFields at all, so creates/updates/patches will work normally.
|
||||
newAccessor, err := meta.Accessor(newObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isResetManagedFields(newAccessor.GetManagedFields()) {
|
||||
return internal.NewEmptyManaged(), nil
|
||||
}
|
||||
|
||||
// If the managed field is empty or we failed to decode it,
|
||||
// let's try the live object. This is to prevent clients who
|
||||
// don't understand managedFields from deleting it accidentally.
|
||||
managed, err := DecodeManagedFields(newAccessor.GetManagedFields())
|
||||
if err != nil || len(managed.Fields()) == 0 {
|
||||
return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields()))
|
||||
}
|
||||
return managed, nil
|
||||
}
|
||||
|
||||
func emptyManagedFieldsOnErr(managed Managed, err error) (Managed, error) {
|
||||
if err != nil {
|
||||
return internal.NewEmptyManaged(), nil
|
||||
}
|
||||
return managed, nil
|
||||
}
|
||||
|
||||
// Update is used when the object has already been merged (non-apply
|
||||
// use-case), and simply updates the managed fields in the output
|
||||
// object.
|
||||
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (object runtime.Object, err error) {
|
||||
// If the object doesn't have metadata, we should just return without trying to
|
||||
// set the managedFields at all, so creates/updates/patches will work normally.
|
||||
newAccessor, err := meta.Accessor(newObj)
|
||||
if err != nil {
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// First try to decode the managed fields provided in the update,
|
||||
// This is necessary to allow directly updating managed fields.
|
||||
var managed Managed
|
||||
if isResetManagedFields(newAccessor.GetManagedFields()) {
|
||||
managed = internal.NewEmptyManaged()
|
||||
} else if managed, err = internal.DecodeObjectManagedFields(newAccessor.GetManagedFields()); err != nil || len(managed.Fields()) == 0 {
|
||||
liveAccessor, err := meta.Accessor(liveObj)
|
||||
if err != nil {
|
||||
return newObj, nil
|
||||
}
|
||||
// If the managed field is empty or we failed to decode it,
|
||||
// let's try the live object. This is to prevent clients who
|
||||
// don't understand managedFields from deleting it accidentally.
|
||||
if managed, err = internal.DecodeObjectManagedFields(liveAccessor.GetManagedFields()); err != nil {
|
||||
managed = internal.NewEmptyManaged()
|
||||
}
|
||||
managed, err := decodeLiveOrNew(liveObj, newObj, f.ignoreManagedFieldsFromRequestObject)
|
||||
if err != nil {
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
internal.RemoveObjectManagedFields(liveObj)
|
||||
@@ -156,9 +188,8 @@ func (f *FieldManager) UpdateNoErrors(liveObj, newObj runtime.Object, manager st
|
||||
obj, err := f.Update(liveObj, newObj, manager)
|
||||
if err != nil {
|
||||
atMostEverySecond.Do(func() {
|
||||
klog.Errorf("[SHOULD NOT HAPPEN] failed to update managedFields for %v: %v",
|
||||
newObj.GetObjectKind().GroupVersionKind(),
|
||||
err)
|
||||
klog.ErrorS(err, "[SHOULD NOT HAPPEN] failed to update managedFields", "VersionKind",
|
||||
newObj.GetObjectKind().GroupVersionKind())
|
||||
})
|
||||
// Explicitly remove managedFields on failure, so that
|
||||
// we can't have garbage in it.
|
||||
@@ -193,14 +224,18 @@ func (f *FieldManager) Apply(liveObj, appliedObj runtime.Object, manager string,
|
||||
}
|
||||
|
||||
// Decode the managed fields in the live object, since it isn't allowed in the patch.
|
||||
managed, err := internal.DecodeObjectManagedFields(accessor.GetManagedFields())
|
||||
managed, err := DecodeManagedFields(accessor.GetManagedFields())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||
}
|
||||
|
||||
internal.RemoveObjectManagedFields(liveObj)
|
||||
|
||||
if object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force); err != nil {
|
||||
object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force)
|
||||
if err != nil {
|
||||
if conflicts, ok := err.(merge.Conflicts); ok {
|
||||
return nil, internal.NewConflictError(conflicts)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
@@ -25,8 +25,8 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/merge"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||
)
|
||||
|
||||
// NewConflictError returns an error including details on the requests apply conflicts
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
// EmptyFields represents a set with no paths
|
||||
|
||||
16
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go
generated
vendored
16
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go
generated
vendored
@@ -22,7 +22,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kube-openapi/pkg/schemaconv"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||
)
|
||||
|
||||
// groupVersionKindExtensionKey is the key used to lookup the
|
||||
@@ -30,12 +30,15 @@ import (
|
||||
// definition's "extensions" map.
|
||||
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
||||
|
||||
type gvkParser struct {
|
||||
// GvkParser contains a Parser that allows introspecting the schema.
|
||||
type GvkParser struct {
|
||||
gvks map[schema.GroupVersionKind]string
|
||||
parser typed.Parser
|
||||
}
|
||||
|
||||
func (p *gvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType {
|
||||
// Type returns a helper which can produce objects of the given type. Any
|
||||
// errors are deferred until a further function is called.
|
||||
func (p *GvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType {
|
||||
typeName, ok := p.gvks[gvk]
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -44,12 +47,15 @@ func (p *gvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType {
|
||||
return &t
|
||||
}
|
||||
|
||||
func newGVKParser(models proto.Models, preserveUnknownFields bool) (*gvkParser, error) {
|
||||
// NewGVKParser builds a GVKParser from a proto.Models. This
|
||||
// will automatically find the proper version of the object, and the
|
||||
// corresponding schema information.
|
||||
func NewGVKParser(models proto.Models, preserveUnknownFields bool) (*GvkParser, error) {
|
||||
typeSchema, err := schemaconv.ToSchemaWithPreserveUnknownFields(models, preserveUnknownFields)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert models to schema: %v", err)
|
||||
}
|
||||
parser := gvkParser{
|
||||
parser := GvkParser{
|
||||
gvks: map[schema.GroupVersionKind]string{},
|
||||
}
|
||||
parser.parser = typed.Parser{Schema: *typeSchema}
|
||||
|
||||
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
@@ -24,7 +24,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
// ManagedInterface groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
||||
@@ -77,15 +77,6 @@ func RemoveObjectManagedFields(obj runtime.Object) {
|
||||
accessor.SetManagedFields(nil)
|
||||
}
|
||||
|
||||
// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields.
|
||||
func DecodeObjectManagedFields(from []metav1.ManagedFieldsEntry) (ManagedInterface, error) {
|
||||
managed, err := decodeManagedFields(from)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert managed fields from API: %v", err)
|
||||
}
|
||||
return &managed, nil
|
||||
}
|
||||
|
||||
// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields
|
||||
func EncodeObjectManagedFields(obj runtime.Object, managed ManagedInterface) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
@@ -102,32 +93,41 @@ func EncodeObjectManagedFields(obj runtime.Object, managed ManagedInterface) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeManagedFields converts ManagedFields from the wire format (api format)
|
||||
// DecodeManagedFields converts ManagedFields from the wire format (api format)
|
||||
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managed managedStruct, err error) {
|
||||
func DecodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (ManagedInterface, error) {
|
||||
managed := managedStruct{}
|
||||
managed.fields = make(fieldpath.ManagedFields, len(encodedManagedFields))
|
||||
managed.times = make(map[string]*metav1.Time, len(encodedManagedFields))
|
||||
|
||||
for i, encodedVersionedSet := range encodedManagedFields {
|
||||
switch encodedVersionedSet.Operation {
|
||||
case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate:
|
||||
default:
|
||||
return nil, fmt.Errorf("operation must be `Apply` or `Update`")
|
||||
}
|
||||
if len(encodedVersionedSet.APIVersion) < 1 {
|
||||
return nil, fmt.Errorf("apiVersion must not be empty")
|
||||
}
|
||||
switch encodedVersionedSet.FieldsType {
|
||||
case "FieldsV1":
|
||||
// Valid case.
|
||||
case "":
|
||||
return managedStruct{}, fmt.Errorf("missing fieldsType in managed fields entry %d", i)
|
||||
return nil, fmt.Errorf("missing fieldsType in managed fields entry %d", i)
|
||||
default:
|
||||
return managedStruct{}, fmt.Errorf("invalid fieldsType %q in managed fields entry %d", encodedVersionedSet.FieldsType, i)
|
||||
return nil, fmt.Errorf("invalid fieldsType %q in managed fields entry %d", encodedVersionedSet.FieldsType, i)
|
||||
}
|
||||
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
|
||||
if err != nil {
|
||||
return managedStruct{}, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
||||
return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
||||
}
|
||||
managed.fields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
||||
if err != nil {
|
||||
return managedStruct{}, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
||||
return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
||||
}
|
||||
managed.times[manager] = encodedVersionedSet.Time
|
||||
}
|
||||
return managed, nil
|
||||
return &managed, nil
|
||||
}
|
||||
|
||||
// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry
|
||||
|
||||
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
@@ -23,8 +23,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/value"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
172
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedmanager.go
generated
vendored
Normal file
172
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedmanager.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fieldmanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||
)
|
||||
|
||||
type lastAppliedManager struct {
|
||||
fieldManager Manager
|
||||
typeConverter TypeConverter
|
||||
objectConverter runtime.ObjectConvertor
|
||||
groupVersion schema.GroupVersion
|
||||
}
|
||||
|
||||
var _ Manager = &lastAppliedManager{}
|
||||
|
||||
// NewLastAppliedManager converts the client-side apply annotation to
|
||||
// server-side apply managed fields
|
||||
func NewLastAppliedManager(fieldManager Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, groupVersion schema.GroupVersion) Manager {
|
||||
return &lastAppliedManager{
|
||||
fieldManager: fieldManager,
|
||||
typeConverter: typeConverter,
|
||||
objectConverter: objectConverter,
|
||||
groupVersion: groupVersion,
|
||||
}
|
||||
}
|
||||
|
||||
// Update implements Manager.
|
||||
func (f *lastAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||
}
|
||||
|
||||
// Apply will consider the last-applied annotation
|
||||
// for upgrading an object managed by client-side apply to server-side apply
|
||||
// without conflicts.
|
||||
func (f *lastAppliedManager) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||
newLiveObj, newManaged, newErr := f.fieldManager.Apply(liveObj, newObj, managed, manager, force)
|
||||
// Upgrade the client-side apply annotation only from kubectl server-side-apply.
|
||||
// To opt-out of this behavior, users may specify a different field manager.
|
||||
if manager != "kubectl" {
|
||||
return newLiveObj, newManaged, newErr
|
||||
}
|
||||
|
||||
// Check if we have conflicts
|
||||
if newErr == nil {
|
||||
return newLiveObj, newManaged, newErr
|
||||
}
|
||||
conflicts, ok := newErr.(merge.Conflicts)
|
||||
if !ok {
|
||||
return newLiveObj, newManaged, newErr
|
||||
}
|
||||
conflictSet := conflictsToSet(conflicts)
|
||||
|
||||
// Check if conflicts are allowed due to client-side apply,
|
||||
// and if so, then force apply
|
||||
allowedConflictSet, err := f.allowedConflictsFromLastApplied(liveObj)
|
||||
if err != nil {
|
||||
return newLiveObj, newManaged, newErr
|
||||
}
|
||||
if !conflictSet.Difference(allowedConflictSet).Empty() {
|
||||
newConflicts := conflictsDifference(conflicts, allowedConflictSet)
|
||||
return newLiveObj, newManaged, newConflicts
|
||||
}
|
||||
|
||||
return f.fieldManager.Apply(liveObj, newObj, managed, manager, true)
|
||||
}
|
||||
|
||||
func (f *lastAppliedManager) allowedConflictsFromLastApplied(liveObj runtime.Object) (*fieldpath.Set, error) {
|
||||
var accessor, err = meta.Accessor(liveObj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
|
||||
// If there is no client-side apply annotation, then there is nothing to do
|
||||
var annotations = accessor.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return nil, fmt.Errorf("no last applied annotation")
|
||||
}
|
||||
var lastApplied, ok = annotations[corev1.LastAppliedConfigAnnotation]
|
||||
if !ok || lastApplied == "" {
|
||||
return nil, fmt.Errorf("no last applied annotation")
|
||||
}
|
||||
|
||||
liveObjVersioned, err := f.objectConverter.ConvertToVersion(liveObj, f.groupVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live obj to versioned: %v", err)
|
||||
}
|
||||
|
||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live obj to typed: %v", err)
|
||||
}
|
||||
|
||||
var lastAppliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
err = json.Unmarshal([]byte(lastApplied), lastAppliedObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode last applied obj: %v in '%s'", err, lastApplied)
|
||||
}
|
||||
|
||||
if lastAppliedObj.GetAPIVersion() != f.groupVersion.String() {
|
||||
return nil, fmt.Errorf("expected version of last applied to match live object '%s', but got '%s': %v", f.groupVersion.String(), lastAppliedObj.GetAPIVersion(), err)
|
||||
}
|
||||
|
||||
lastAppliedObjTyped, err := f.typeConverter.ObjectToTyped(lastAppliedObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert last applied to typed: %v", err)
|
||||
}
|
||||
|
||||
lastAppliedObjFieldSet, err := lastAppliedObjTyped.ToFieldSet()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create fieldset for last applied object: %v", err)
|
||||
}
|
||||
|
||||
comparison, err := lastAppliedObjTyped.Compare(liveObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compare last applied object and live object: %v", err)
|
||||
}
|
||||
|
||||
// Remove fields in last applied that are different, added, or missing in
|
||||
// the live object.
|
||||
// Because last-applied fields don't match the live object fields,
|
||||
// then we don't own these fields.
|
||||
lastAppliedObjFieldSet = lastAppliedObjFieldSet.
|
||||
Difference(comparison.Modified).
|
||||
Difference(comparison.Added).
|
||||
Difference(comparison.Removed)
|
||||
|
||||
return lastAppliedObjFieldSet, nil
|
||||
}
|
||||
|
||||
// TODO: replace with merge.Conflicts.ToSet()
|
||||
func conflictsToSet(conflicts merge.Conflicts) *fieldpath.Set {
|
||||
conflictSet := fieldpath.NewSet()
|
||||
for _, conflict := range []merge.Conflict(conflicts) {
|
||||
conflictSet.Insert(conflict.Path)
|
||||
}
|
||||
return conflictSet
|
||||
}
|
||||
|
||||
func conflictsDifference(conflicts merge.Conflicts, s *fieldpath.Set) merge.Conflicts {
|
||||
newConflicts := []merge.Conflict{}
|
||||
for _, conflict := range []merge.Conflict(conflicts) {
|
||||
if !s.Has(conflict.Path) {
|
||||
newConflicts = append(newConflicts, conflict)
|
||||
}
|
||||
}
|
||||
return newConflicts
|
||||
}
|
||||
133
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedupdater.go
generated
vendored
Normal file
133
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/lastappliedupdater.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fieldmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
const totalAnnotationSizeLimitB int64 = 256 * (1 << 10) // 256 kB
|
||||
|
||||
type lastAppliedUpdater struct {
|
||||
fieldManager Manager
|
||||
}
|
||||
|
||||
var _ Manager = &lastAppliedUpdater{}
|
||||
|
||||
// NewLastAppliedUpdater sets the client-side apply annotation up to date with
|
||||
// server-side apply managed fields
|
||||
func NewLastAppliedUpdater(fieldManager Manager) Manager {
|
||||
return &lastAppliedUpdater{
|
||||
fieldManager: fieldManager,
|
||||
}
|
||||
}
|
||||
|
||||
// Update implements Manager.
|
||||
func (f *lastAppliedUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||
return f.fieldManager.Update(liveObj, newObj, managed, manager)
|
||||
}
|
||||
|
||||
// server-side apply managed fields
|
||||
func (f *lastAppliedUpdater) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
|
||||
liveObj, managed, err := f.fieldManager.Apply(liveObj, newObj, managed, manager, force)
|
||||
if err != nil {
|
||||
return liveObj, managed, err
|
||||
}
|
||||
|
||||
// Sync the client-side apply annotation only from kubectl server-side apply.
|
||||
// To opt-out of this behavior, users may specify a different field manager.
|
||||
//
|
||||
// If the client-side apply annotation doesn't exist,
|
||||
// then continue because we have no annotation to update
|
||||
if manager == "kubectl" && hasLastApplied(liveObj) {
|
||||
lastAppliedValue, err := buildLastApplied(newObj)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err)
|
||||
}
|
||||
err = setLastApplied(liveObj, lastAppliedValue)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err)
|
||||
}
|
||||
}
|
||||
return liveObj, managed, err
|
||||
}
|
||||
|
||||
func hasLastApplied(obj runtime.Object) bool {
|
||||
var accessor, err = meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
var annotations = accessor.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
lastApplied, ok := annotations[corev1.LastAppliedConfigAnnotation]
|
||||
return ok && len(lastApplied) > 0
|
||||
}
|
||||
|
||||
func setLastApplied(obj runtime.Object, value string) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
var annotations = accessor.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[corev1.LastAppliedConfigAnnotation] = value
|
||||
if isAnnotationsValid(annotations) != nil {
|
||||
delete(annotations, corev1.LastAppliedConfigAnnotation)
|
||||
}
|
||||
accessor.SetAnnotations(annotations)
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildLastApplied(obj runtime.Object) (string, error) {
|
||||
obj = obj.DeepCopyObject()
|
||||
|
||||
var accessor, err = meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
|
||||
// Remove the annotation from the object before encoding the object
|
||||
var annotations = accessor.GetAnnotations()
|
||||
delete(annotations, corev1.LastAppliedConfigAnnotation)
|
||||
accessor.SetAnnotations(annotations)
|
||||
|
||||
lastApplied, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("couldn't encode object into last applied annotation: %v", err)
|
||||
}
|
||||
return string(lastApplied), nil
|
||||
}
|
||||
|
||||
func isAnnotationsValid(annotations map[string]string) error {
|
||||
var totalSize int64
|
||||
for k, v := range annotations {
|
||||
totalSize += (int64)(len(k)) + (int64)(len(v))
|
||||
}
|
||||
if totalSize > (int64)(totalAnnotationSizeLimitB) {
|
||||
return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, totalAnnotationSizeLimitB)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/managedfieldsupdater.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/managedfieldsupdater.go
generated
vendored
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
type managedFieldsUpdater struct {
|
||||
@@ -44,6 +44,7 @@ func NewManagedFieldsUpdater(fieldManager Manager) Manager {
|
||||
// Update implements Manager.
|
||||
func (f *managedFieldsUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
|
||||
self := "current-operation"
|
||||
formerSet := managed.Fields()[manager]
|
||||
object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, self)
|
||||
if err != nil {
|
||||
return object, managed, err
|
||||
@@ -54,12 +55,15 @@ func (f *managedFieldsUpdater) Update(liveObj, newObj runtime.Object, managed Ma
|
||||
if vs, ok := managed.Fields()[self]; ok {
|
||||
delete(managed.Fields(), self)
|
||||
|
||||
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
|
||||
if previous, ok := managed.Fields()[manager]; ok {
|
||||
managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied())
|
||||
} else {
|
||||
managed.Fields()[manager] = vs
|
||||
}
|
||||
// Update the time only if the manager's fieldSet has changed.
|
||||
if formerSet == nil || !managed.Fields()[manager].Set().Equals(formerSet.Set()) {
|
||||
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
|
||||
}
|
||||
}
|
||||
|
||||
return object, managed, nil
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/node.yaml
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/node.yaml
generated
vendored
@@ -15,6 +15,8 @@ metadata:
|
||||
cloud.google.com/gke-os-distribution: cos
|
||||
failure-domain.beta.kubernetes.io/region: us-central1
|
||||
failure-domain.beta.kubernetes.io/zone: us-central1-b
|
||||
topology.kubernetes.io/region: us-central1
|
||||
topology.kubernetes.io/zone: us-central1-b
|
||||
kubernetes.io/hostname: node-default-pool-something
|
||||
name: node-default-pool-something
|
||||
resourceVersion: "211582541"
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/stripmeta.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/stripmeta.go
generated
vendored
@@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
type stripMetaManager struct {
|
||||
|
||||
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go
generated
vendored
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go
generated
vendored
@@ -24,13 +24,12 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/merge"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||
)
|
||||
|
||||
type structuredMergeManager struct {
|
||||
typeConverter internal.TypeConverter
|
||||
typeConverter TypeConverter
|
||||
objectConverter runtime.ObjectConvertor
|
||||
objectDefaulter runtime.ObjectDefaulter
|
||||
groupVersion schema.GroupVersion
|
||||
@@ -42,12 +41,7 @@ var _ Manager = &structuredMergeManager{}
|
||||
|
||||
// NewStructuredMergeManager creates a new Manager that merges apply requests
|
||||
// and update managed fields for other types of requests.
|
||||
func NewStructuredMergeManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (Manager, error) {
|
||||
typeConverter, err := internal.NewTypeConverter(models, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (Manager, error) {
|
||||
return &structuredMergeManager{
|
||||
typeConverter: typeConverter,
|
||||
objectConverter: objectConverter,
|
||||
@@ -55,7 +49,8 @@ func NewStructuredMergeManager(models openapiproto.Models, objectConverter runti
|
||||
groupVersion: gv,
|
||||
hubVersion: hub,
|
||||
updater: merge.Updater{
|
||||
Converter: internal.NewVersionConverter(typeConverter, objectConverter, hub),
|
||||
Converter: newVersionConverter(typeConverter, objectConverter, hub), // This is the converter provided to SMD from k8s
|
||||
IgnoredFields: resetFields,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -63,14 +58,7 @@ func NewStructuredMergeManager(models openapiproto.Models, objectConverter runti
|
||||
// NewCRDStructuredMergeManager creates a new Manager specifically for
|
||||
// CRDs. This allows for the possibility of fields which are not defined
|
||||
// in models, as well as having no models defined at all.
|
||||
func NewCRDStructuredMergeManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, preserveUnknownFields bool) (_ Manager, err error) {
|
||||
var typeConverter internal.TypeConverter = internal.DeducedTypeConverter{}
|
||||
if models != nil {
|
||||
typeConverter, err = internal.NewTypeConverter(models, preserveUnknownFields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
func NewCRDStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ Manager, err error) {
|
||||
return &structuredMergeManager{
|
||||
typeConverter: typeConverter,
|
||||
objectConverter: objectConverter,
|
||||
@@ -78,7 +66,8 @@ func NewCRDStructuredMergeManager(models openapiproto.Models, objectConverter ru
|
||||
groupVersion: gv,
|
||||
hubVersion: hub,
|
||||
updater: merge.Updater{
|
||||
Converter: internal.NewCRDVersionConverter(typeConverter, objectConverter, hub),
|
||||
Converter: newCRDVersionConverter(typeConverter, objectConverter, hub),
|
||||
IgnoredFields: resetFields,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -129,7 +118,7 @@ func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed
|
||||
return nil, nil, fmt.Errorf("couldn't get accessor: %v", err)
|
||||
}
|
||||
if patchObjMeta.GetManagedFields() != nil {
|
||||
return nil, nil, errors.NewBadRequest(fmt.Sprintf("metadata.managedFields must be nil"))
|
||||
return nil, nil, errors.NewBadRequest("metadata.managedFields must be nil")
|
||||
}
|
||||
|
||||
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||
@@ -149,9 +138,6 @@ func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed
|
||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||
newObjTyped, managedFields, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed.Fields(), manager, force)
|
||||
if err != nil {
|
||||
if conflicts, ok := err.(merge.Conflicts); ok {
|
||||
return nil, nil, internal.NewConflictError(conflicts)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
managed = internal.NewManaged(managedFields, managed.Times())
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
package fieldmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -22,9 +22,10 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/value"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||
)
|
||||
|
||||
// TypeConverter allows you to convert from runtime.Object to
|
||||
@@ -64,7 +65,7 @@ func (DeducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Obje
|
||||
}
|
||||
|
||||
type typeConverter struct {
|
||||
parser *gvkParser
|
||||
parser *internal.GvkParser
|
||||
}
|
||||
|
||||
var _ TypeConverter = &typeConverter{}
|
||||
@@ -73,7 +74,7 @@ var _ TypeConverter = &typeConverter{}
|
||||
// will automatically find the proper version of the object, and the
|
||||
// corresponding schema information.
|
||||
func NewTypeConverter(models proto.Models, preserveUnknownFields bool) (TypeConverter, error) {
|
||||
parser, err := newGVKParser(models, preserveUnknownFields)
|
||||
parser, err := internal.NewGVKParser(models, preserveUnknownFields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
package fieldmanager
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/merge"
|
||||
"sigs.k8s.io/structured-merge-diff/v3/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/merge"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||
)
|
||||
|
||||
// versionConverter is an implementation of
|
||||
@@ -35,7 +35,7 @@ type versionConverter struct {
|
||||
var _ merge.Converter = &versionConverter{}
|
||||
|
||||
// NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor.
|
||||
func NewVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
func newVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
return &versionConverter{
|
||||
typeConverter: t,
|
||||
objectConvertor: o,
|
||||
@@ -49,7 +49,7 @@ func NewVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.Gr
|
||||
}
|
||||
|
||||
// NewCRDVersionConverter builds a VersionConverter for CRDs from a TypeConverter and an ObjectConvertor.
|
||||
func NewCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
func newCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
return &versionConverter{
|
||||
typeConverter: t,
|
||||
objectConvertor: o,
|
||||
37
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
37
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
@@ -25,7 +25,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
@@ -49,7 +52,7 @@ type getterFunc func(ctx context.Context, name string, req *http.Request, trace
|
||||
// passed-in getterFunc to perform the actual get.
|
||||
func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
trace := utiltrace.New("Get", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}})
|
||||
trace := utiltrace.New("Get", traceFields(req)...)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
@@ -79,22 +82,22 @@ func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc
|
||||
}
|
||||
|
||||
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
|
||||
func GetResource(r rest.Getter, e rest.Exporter, scope *RequestScope) http.HandlerFunc {
|
||||
func GetResource(r rest.Getter, scope *RequestScope) http.HandlerFunc {
|
||||
return getResourceHandler(scope,
|
||||
func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) {
|
||||
// check for export
|
||||
options := metav1.GetOptions{}
|
||||
if values := req.URL.Query(); len(values) > 0 {
|
||||
exports := metav1.ExportOptions{}
|
||||
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &exports); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
if exports.Export {
|
||||
if e == nil {
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("export of %q is not supported", scope.Resource.Resource))
|
||||
if len(values["export"]) > 0 {
|
||||
exportBool := true
|
||||
exportStrings := values["export"]
|
||||
err := runtime.Convert_Slice_string_To_bool(&exportStrings, &exportBool, nil)
|
||||
if err != nil {
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("the export parameter cannot be parsed: %v", err))
|
||||
}
|
||||
if exportBool {
|
||||
return nil, errors.NewBadRequest("the export parameter, deprecated since v1.14, is no longer supported")
|
||||
}
|
||||
return e.Export(ctx, name, exports)
|
||||
}
|
||||
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
@@ -166,7 +169,7 @@ func getRequestOptions(req *http.Request, scope *RequestScope, into runtime.Obje
|
||||
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", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}})
|
||||
trace := utiltrace.New("List", traceFields(req)...)
|
||||
|
||||
namespace, err := scope.Namer.Namespace(req)
|
||||
if err != nil {
|
||||
@@ -198,6 +201,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatc
|
||||
return
|
||||
}
|
||||
|
||||
if errs := metainternalversionvalidation.ValidateListOptions(&opts); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// transform fields
|
||||
// TODO: DecodeParametersInto should do this.
|
||||
if opts.FieldSelector != nil {
|
||||
@@ -248,7 +257,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatc
|
||||
if timeout == 0 && minRequestTimeout > 0 {
|
||||
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)
|
||||
klog.V(3).InfoS("Starting watch", "path", req.URL.Path, "resourceVersion", opts.ResourceVersion, "labels", opts.LabelSelector, "fields", opts.FieldSelector, "timeout", timeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
watcher, err := rw.Watch(ctx, &opts)
|
||||
|
||||
15
vendor/k8s.io/apiserver/pkg/endpoints/handlers/helpers.go
generated
vendored
15
vendor/k8s.io/apiserver/pkg/endpoints/handlers/helpers.go
generated
vendored
@@ -58,3 +58,18 @@ func (lazy *lazyClientIP) String() string {
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// lazyAccept implements String() string and it will
|
||||
// calls http.Request Header.Get() lazily only when required.
|
||||
type lazyAccept struct {
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
func (lazy *lazyAccept) String() string {
|
||||
if lazy.req != nil {
|
||||
accept := lazy.req.Header.Get("Accept")
|
||||
return accept
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
44
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
44
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
@@ -38,6 +38,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
@@ -49,7 +50,6 @@ import (
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -61,7 +61,7 @@ const (
|
||||
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", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}})
|
||||
trace := utiltrace.New("Patch", traceFields(req)...)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -84,19 +84,17 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we
|
||||
// document, move timeout out of this function and declare it in
|
||||
// api_installer)
|
||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
|
||||
// timeout inside the parent context is lower than requestTimeoutUpperBound.
|
||||
ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound)
|
||||
defer cancel()
|
||||
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
@@ -173,6 +171,9 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
|
||||
userInfo,
|
||||
)
|
||||
|
||||
if scope.FieldManager != nil {
|
||||
admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
|
||||
}
|
||||
mutatingAdmission, _ := admit.(admission.MutationInterface)
|
||||
createAuthorizerAttributes := authorizer.AttributesRecord{
|
||||
User: userInfo,
|
||||
@@ -208,7 +209,6 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
|
||||
|
||||
codec: codec,
|
||||
|
||||
timeout: timeout,
|
||||
options: options,
|
||||
|
||||
restPatcher: r,
|
||||
@@ -271,7 +271,6 @@ type patcher struct {
|
||||
|
||||
codec runtime.Codec
|
||||
|
||||
timeout time.Duration
|
||||
options *metav1.PatchOptions
|
||||
|
||||
// Operation information
|
||||
@@ -421,6 +420,7 @@ type applyPatcher struct {
|
||||
creater runtime.ObjectCreater
|
||||
kind schema.GroupVersionKind
|
||||
fieldManager *fieldmanager.FieldManager
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) {
|
||||
@@ -569,14 +569,20 @@ func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runti
|
||||
options: p.options,
|
||||
creater: p.creater,
|
||||
kind: p.kind,
|
||||
userAgent: p.userAgent,
|
||||
}
|
||||
p.forceAllowCreate = true
|
||||
default:
|
||||
return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType)
|
||||
}
|
||||
dedupOwnerReferencesTransformer := func(_ context.Context, obj, _ runtime.Object) (runtime.Object, error) {
|
||||
// Dedup owner references after mutating admission happens
|
||||
dedupOwnerReferencesAndAddWarning(obj, ctx, true)
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
wasCreated := false
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission, dedupOwnerReferencesTransformer)
|
||||
requestFunc := func() (runtime.Object, error) {
|
||||
// Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate
|
||||
options := patchToUpdateOptions(p.options)
|
||||
@@ -584,17 +590,21 @@ func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runti
|
||||
wasCreated = created
|
||||
return updateObject, updateErr
|
||||
}
|
||||
result, err := finishRequest(p.timeout, func() (runtime.Object, error) {
|
||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
||||
result, err := requestFunc()
|
||||
// If the object wasn't committed to storage because it's serialized size was too large,
|
||||
// it is safe to remove managedFields (which can be large) and try again.
|
||||
if isTooLargeError(err) && p.patchType != types.ApplyPatchType {
|
||||
if _, accessorErr := meta.Accessor(p.restPatcher.New()); accessorErr == nil {
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission, func(_ context.Context, obj, _ runtime.Object) (runtime.Object, error) {
|
||||
accessor, _ := meta.Accessor(obj)
|
||||
accessor.SetManagedFields(nil)
|
||||
return obj, nil
|
||||
})
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil,
|
||||
p.applyPatch,
|
||||
p.applyAdmission,
|
||||
dedupOwnerReferencesTransformer,
|
||||
func(_ context.Context, obj, _ runtime.Object) (runtime.Object, error) {
|
||||
accessor, _ := meta.Accessor(obj)
|
||||
accessor.SetManagedFields(nil)
|
||||
return obj, nil
|
||||
})
|
||||
result, err = requestFunc()
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
generated
vendored
@@ -68,7 +68,7 @@ func ErrorToAPIStatus(err error) *metav1.Status {
|
||||
// by REST storage - these typically indicate programmer
|
||||
// error by not using pkg/api/errors, or unexpected failure
|
||||
// cases.
|
||||
runtime.HandleError(fmt.Errorf("apiserver received an error that is not an metav1.Status: %#+v", err))
|
||||
runtime.HandleError(fmt.Errorf("apiserver received an error that is not an metav1.Status: %#+v: %v", err, err))
|
||||
return &metav1.Status{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Status",
|
||||
|
||||
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
34
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
@@ -21,11 +21,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/apiserver/pkg/util/flushwriter"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// StreamObject performs input stream negotiation from a ResourceStreamer and writes that to the response.
|
||||
@@ -86,19 +87,30 @@ func StreamObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSe
|
||||
// The context is optional and can be nil. This method will perform optional content compression if requested by
|
||||
// a client and the feature gate for APIResponseCompression is enabled.
|
||||
func SerializeObject(mediaType string, encoder runtime.Encoder, hw http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
trace := utiltrace.New("SerializeObject",
|
||||
utiltrace.Field{"method", req.Method},
|
||||
utiltrace.Field{"url", req.URL.Path},
|
||||
utiltrace.Field{"protocol", req.Proto},
|
||||
utiltrace.Field{"mediaType", mediaType},
|
||||
utiltrace.Field{"encoder", encoder.Identifier()})
|
||||
defer trace.LogIfLong(5 * time.Second)
|
||||
|
||||
w := &deferredResponseWriter{
|
||||
mediaType: mediaType,
|
||||
statusCode: statusCode,
|
||||
contentEncoding: negotiateContentEncoding(req),
|
||||
hw: hw,
|
||||
trace: trace,
|
||||
}
|
||||
|
||||
err := encoder.Encode(object, w)
|
||||
if err == nil {
|
||||
err = w.Close()
|
||||
if err == nil {
|
||||
return
|
||||
if err != nil {
|
||||
// we cannot write an error to the writer anymore as the Encode call was successful.
|
||||
utilruntime.HandleError(fmt.Errorf("apiserver was unable to close cleanly the response writer: %v", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// make a best effort to write the object if a failure is detected
|
||||
@@ -175,9 +187,23 @@ type deferredResponseWriter struct {
|
||||
hasWritten bool
|
||||
hw http.ResponseWriter
|
||||
w io.Writer
|
||||
|
||||
trace *utiltrace.Trace
|
||||
}
|
||||
|
||||
func (w *deferredResponseWriter) Write(p []byte) (n int, err error) {
|
||||
if w.trace != nil {
|
||||
// This Step usually wraps in-memory object serialization.
|
||||
w.trace.Step("About to start writing response", utiltrace.Field{"size", len(p)})
|
||||
|
||||
firstWrite := !w.hasWritten
|
||||
defer func() {
|
||||
w.trace.Step("Write call finished",
|
||||
utiltrace.Field{"writer", fmt.Sprintf("%T", w.w)},
|
||||
utiltrace.Field{"size", len(p)},
|
||||
utiltrace.Field{"firstWrite", firstWrite})
|
||||
}()
|
||||
}
|
||||
if w.hasWritten {
|
||||
return w.w.Write(p)
|
||||
}
|
||||
@@ -217,8 +243,6 @@ func (w *deferredResponseWriter) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var nopCloser = ioutil.NopCloser(nil)
|
||||
|
||||
// WriteObjectNegotiated renders an object in the content type negotiated by the client.
|
||||
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)
|
||||
|
||||
107
vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
generated
vendored
107
vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
generated
vendored
@@ -31,12 +31,14 @@ import (
|
||||
grpccodes "google.golang.org/grpc/codes"
|
||||
grpcstatus "google.golang.org/grpc/status"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
@@ -46,7 +48,22 @@ import (
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// 34 chose as a number close to 30 that is likely to be unique enough to jump out at me the next time I see a timeout.
|
||||
// Everyone chooses 30.
|
||||
requestTimeoutUpperBound = 34 * time.Second
|
||||
// DuplicateOwnerReferencesWarningFormat is the warning that a client receives when a create/update request contains
|
||||
// duplicate owner reference entries.
|
||||
DuplicateOwnerReferencesWarningFormat = ".metadata.ownerReferences contains duplicate entries; API server dedups owner references in 1.20+, and may reject such requests as early as 1.24; please fix your requests; duplicate UID(s) observed: %v"
|
||||
// DuplicateOwnerReferencesAfterMutatingAdmissionWarningFormat indicates the duplication was observed
|
||||
// after mutating admission.
|
||||
// NOTE: For CREATE and UPDATE requests the API server dedups both before and after mutating admission.
|
||||
// For PATCH request the API server only dedups after mutating admission.
|
||||
DuplicateOwnerReferencesAfterMutatingAdmissionWarningFormat = ".metadata.ownerReferences contains duplicate entries after mutating admission happens; API server dedups owner references in 1.20+, and may reject such requests as early as 1.24; please fix your requests; duplicate UID(s) observed: %v"
|
||||
)
|
||||
|
||||
// RequestScope encapsulates common fields across all RESTful handler methods.
|
||||
@@ -213,7 +230,7 @@ type resultFunc func() (runtime.Object, error)
|
||||
|
||||
// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response.
|
||||
// An api.Status object with status != success is considered an "error", which interrupts the normal response flow.
|
||||
func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object, err error) {
|
||||
func finishRequest(ctx context.Context, fn resultFunc) (result runtime.Object, err error) {
|
||||
// these channels need to be buffered to prevent the goroutine below from hanging indefinitely
|
||||
// when the select statement reads something other than the one the goroutine sends on.
|
||||
ch := make(chan runtime.Object, 1)
|
||||
@@ -257,8 +274,8 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
|
||||
return nil, err
|
||||
case p := <-panicCh:
|
||||
panic(p)
|
||||
case <-time.After(timeout):
|
||||
return nil, errors.NewTimeoutError(fmt.Sprintf("request did not complete within requested timeout %s", timeout), 0)
|
||||
case <-ctx.Done():
|
||||
return nil, errors.NewTimeoutError(fmt.Sprintf("request did not complete within requested timeout %s", ctx.Err()), 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,11 +340,79 @@ func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// dedupOwnerReferences dedups owner references over the entire entry.
|
||||
// NOTE: We don't know enough about the existing cases of owner references
|
||||
// sharing the same UID but different fields. Nor do we know what might break.
|
||||
// In the future we may just dedup/reject owner references with the same UID.
|
||||
func dedupOwnerReferences(refs []metav1.OwnerReference) ([]metav1.OwnerReference, []string) {
|
||||
var result []metav1.OwnerReference
|
||||
var duplicates []string
|
||||
seen := make(map[types.UID]struct{})
|
||||
for _, ref := range refs {
|
||||
_, ok := seen[ref.UID]
|
||||
// Short-circuit if we haven't seen the UID before. Otherwise
|
||||
// check the entire list we have so far.
|
||||
if !ok || !hasOwnerReference(result, ref) {
|
||||
seen[ref.UID] = struct{}{}
|
||||
result = append(result, ref)
|
||||
} else {
|
||||
duplicates = append(duplicates, string(ref.UID))
|
||||
}
|
||||
}
|
||||
return result, duplicates
|
||||
}
|
||||
|
||||
// hasOwnerReference returns true if refs has an item equal to ref. The function
|
||||
// focuses on semantic equality instead of memory equality, to catch duplicates
|
||||
// with different pointer addresses. The function uses apiequality.Semantic
|
||||
// instead of implementing its own comparison, to tolerate API changes to
|
||||
// metav1.OwnerReference.
|
||||
// NOTE: This is expensive, but we accept it because we've made sure it only
|
||||
// happens to owner references containing duplicate UIDs, plus typically the
|
||||
// number of items in the list should be small.
|
||||
func hasOwnerReference(refs []metav1.OwnerReference, ref metav1.OwnerReference) bool {
|
||||
for _, r := range refs {
|
||||
if apiequality.Semantic.DeepEqual(r, ref) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// dedupOwnerReferencesAndAddWarning dedups owner references in the object metadata.
|
||||
// If duplicates are found, the function records a warning to the provided context.
|
||||
func dedupOwnerReferencesAndAddWarning(obj runtime.Object, requestContext context.Context, afterMutatingAdmission bool) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
// The object doesn't have metadata. Nothing we need to do here.
|
||||
return
|
||||
}
|
||||
refs := accessor.GetOwnerReferences()
|
||||
deduped, duplicates := dedupOwnerReferences(refs)
|
||||
if len(duplicates) > 0 {
|
||||
// NOTE: For CREATE and UPDATE requests the API server dedups both before and after mutating admission.
|
||||
// For PATCH request the API server only dedups after mutating admission.
|
||||
format := DuplicateOwnerReferencesWarningFormat
|
||||
if afterMutatingAdmission {
|
||||
format = DuplicateOwnerReferencesAfterMutatingAdmissionWarningFormat
|
||||
}
|
||||
warning.AddWarning(requestContext, "", fmt.Sprintf(format,
|
||||
strings.Join(duplicates, ", ")))
|
||||
accessor.SetOwnerReferences(deduped)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.RemoveSelfLink) {
|
||||
// Ensure that for empty lists we don't return <nil> items.
|
||||
if meta.IsListType(obj) && meta.LenList(obj) == 0 {
|
||||
if err := meta.SetList(obj, []runtime.Object{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -347,7 +432,7 @@ func setObjectSelfLink(ctx context.Context, obj runtime.Object, req *http.Reques
|
||||
return err
|
||||
}
|
||||
if err := namer.SetSelfLink(obj, uri); err != nil {
|
||||
klog.V(4).Infof("Unable to set self link on object: %v", err)
|
||||
klog.V(4).InfoS("Unable to set self link on object", "error", err)
|
||||
}
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
@@ -405,18 +490,6 @@ func limitedReadBody(req *http.Request, limit int64) ([]byte, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func parseTimeout(str string) time.Duration {
|
||||
if str != "" {
|
||||
timeout, err := time.ParseDuration(str)
|
||||
if err == nil {
|
||||
return timeout
|
||||
}
|
||||
klog.Errorf("Failed to parse %q: %v", str, err)
|
||||
}
|
||||
// 34 chose as a number close to 30 that is likely to be unique enough to jump out at me the next time I see a timeout. Everyone chooses 30.
|
||||
return 34 * time.Second
|
||||
}
|
||||
|
||||
func isDryRun(url *url.URL) bool {
|
||||
return len(url.Query()["dryRun"]) != 0
|
||||
}
|
||||
|
||||
32
vendor/k8s.io/apiserver/pkg/endpoints/handlers/trace_util.go
generated
vendored
Normal file
32
vendor/k8s.io/apiserver/pkg/endpoints/handlers/trace_util.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
func traceFields(req *http.Request) []utiltrace.Field {
|
||||
return []utiltrace.Field{
|
||||
{Key: "url", Value: req.URL.Path},
|
||||
{Key: "user-agent", Value: &lazyTruncatedUserAgent{req: req}},
|
||||
{Key: "client", Value: &lazyClientIP{req: req}},
|
||||
{Key: "accept", Value: &lazyAccept{req: req}},
|
||||
{Key: "protocol", Value: req.Proto}}
|
||||
}
|
||||
22
vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
generated
vendored
22
vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
generated
vendored
@@ -33,6 +33,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
@@ -46,7 +47,7 @@ import (
|
||||
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", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}})
|
||||
trace := utiltrace.New("Update", traceFields(req)...)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
@@ -54,16 +55,17 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
||||
|
||||
// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
|
||||
// timeout inside the parent context is lower than requestTimeoutUpperBound.
|
||||
ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound)
|
||||
defer cancel()
|
||||
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
|
||||
@@ -129,6 +131,7 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
|
||||
// allows skipping managedFields update if the resulting object is too big
|
||||
shouldUpdateManagedFields := true
|
||||
if scope.FieldManager != nil {
|
||||
admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
|
||||
transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
|
||||
if shouldUpdateManagedFields {
|
||||
return scope.FieldManager.UpdateNoErrors(liveObj, newObj, managerOrUserAgent(options.FieldManager, req.UserAgent())), nil
|
||||
@@ -153,6 +156,11 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
|
||||
}
|
||||
return newObj, nil
|
||||
})
|
||||
transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
|
||||
// Dedup owner references again after mutating admission happens
|
||||
dedupOwnerReferencesAndAddWarning(newObj, req.Context(), true)
|
||||
return newObj, nil
|
||||
})
|
||||
}
|
||||
|
||||
createAuthorizerAttributes := authorizer.AttributesRecord{
|
||||
@@ -188,7 +196,9 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
|
||||
wasCreated = created
|
||||
return obj, err
|
||||
}
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
// Dedup owner references before updating managed fields
|
||||
dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
|
||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
||||
result, err := requestFunc()
|
||||
// If the object wasn't committed to storage because it's serialized size was too large,
|
||||
// it is safe to remove managedFields (which can be large) and try again.
|
||||
|
||||
8
vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
generated
vendored
@@ -163,8 +163,8 @@ type WatchServer struct {
|
||||
// or over a websocket connection.
|
||||
func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
kind := s.Scope.Kind
|
||||
metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc()
|
||||
defer metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Dec()
|
||||
metrics.RegisteredWatchers.WithContext(req.Context()).WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc()
|
||||
defer metrics.RegisteredWatchers.WithContext(req.Context()).WithLabelValues(kind.Group, kind.Version, kind.Kind).Dec()
|
||||
|
||||
w = httplog.Unlogged(req, w)
|
||||
|
||||
@@ -220,7 +220,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// End of results.
|
||||
return
|
||||
}
|
||||
metrics.WatchEvents.WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc()
|
||||
metrics.WatchEvents.WithContext(req.Context()).WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc()
|
||||
|
||||
obj := s.Fixup(event.Object)
|
||||
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||
@@ -233,7 +233,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// type
|
||||
unknown.Raw = buf.Bytes()
|
||||
event.Object = &unknown
|
||||
metrics.WatchEventsSizes.WithLabelValues(kind.Group, kind.Version, kind.Kind).Observe(float64(len(unknown.Raw)))
|
||||
metrics.WatchEventsSizes.WithContext(req.Context()).WithLabelValues(kind.Group, kind.Version, kind.Kind).Observe(float64(len(unknown.Raw)))
|
||||
|
||||
*outEvent = metav1.WatchEvent{}
|
||||
|
||||
|
||||
251
vendor/k8s.io/apiserver/pkg/endpoints/installer.go
generated
vendored
251
vendor/k8s.io/apiserver/pkg/endpoints/installer.go
generated
vendored
@@ -32,15 +32,21 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/endpoints/deprecation"
|
||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
utilwarning "k8s.io/apiserver/pkg/endpoints/warning"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storageversion"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
versioninfo "k8s.io/component-base/version"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -90,8 +96,9 @@ var toDiscoveryKubeVerb = map[string]string{
|
||||
}
|
||||
|
||||
// Install handlers for API resources.
|
||||
func (a *APIInstaller) Install() ([]metav1.APIResource, *restful.WebService, []error) {
|
||||
func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error) {
|
||||
var apiResources []metav1.APIResource
|
||||
var resourceInfos []*storageversion.ResourceInfo
|
||||
var errors []error
|
||||
ws := a.newWebService()
|
||||
|
||||
@@ -104,15 +111,18 @@ func (a *APIInstaller) Install() ([]metav1.APIResource, *restful.WebService, []e
|
||||
}
|
||||
sort.Strings(paths)
|
||||
for _, path := range paths {
|
||||
apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
|
||||
apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
|
||||
}
|
||||
if apiResource != nil {
|
||||
apiResources = append(apiResources, *apiResource)
|
||||
}
|
||||
if resourceInfo != nil {
|
||||
resourceInfos = append(resourceInfos, resourceInfo)
|
||||
}
|
||||
}
|
||||
return apiResources, ws, errors
|
||||
return apiResources, resourceInfos, ws, errors
|
||||
}
|
||||
|
||||
// newWebService creates a new restful webservice with the api installer's prefix and version.
|
||||
@@ -178,7 +188,7 @@ func GetResourceKind(groupVersion schema.GroupVersion, storage rest.Storage, typ
|
||||
return fqKindToRegister, nil
|
||||
}
|
||||
|
||||
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, error) {
|
||||
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
|
||||
admit := a.group.Admit
|
||||
|
||||
optionsExternalVersion := a.group.GroupVersion
|
||||
@@ -188,19 +198,19 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
|
||||
resource, subresource, err := splitSubresource(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
group, version := a.group.GroupVersion.Group, a.group.GroupVersion.Version
|
||||
|
||||
fqKindToRegister, err := GetResourceKind(a.group.GroupVersion, storage, a.group.Typer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
versionedPtr, err := a.group.Creater.New(fqKindToRegister)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
defaultVersionedObject := indirectArbitraryPointer(versionedPtr)
|
||||
kind := fqKindToRegister.Kind
|
||||
@@ -211,18 +221,18 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isSubresource {
|
||||
parentStorage, ok := a.group.Storage[resource]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing parent storage: %q", resource)
|
||||
return nil, nil, fmt.Errorf("missing parent storage: %q", resource)
|
||||
}
|
||||
scoper, ok := parentStorage.(rest.Scoper)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q must implement scoper", resource)
|
||||
return nil, nil, fmt.Errorf("%q must implement scoper", resource)
|
||||
}
|
||||
namespaceScoped = scoper.NamespaceScoped()
|
||||
|
||||
} else {
|
||||
scoper, ok := storage.(rest.Scoper)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q must implement scoper", resource)
|
||||
return nil, nil, fmt.Errorf("%q must implement scoper", resource)
|
||||
}
|
||||
namespaceScoped = scoper.NamespaceScoped()
|
||||
}
|
||||
@@ -244,49 +254,47 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if !isMetadata {
|
||||
storageMeta = defaultStorageMetadata{}
|
||||
}
|
||||
exporter, isExporter := storage.(rest.Exporter)
|
||||
if !isExporter {
|
||||
exporter = nil
|
||||
}
|
||||
|
||||
versionedExportOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ExportOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isNamedCreater {
|
||||
isCreater = true
|
||||
}
|
||||
|
||||
var resetFields map[fieldpath.APIVersion]*fieldpath.Set
|
||||
if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
if resetFieldsStrategy, isResetFieldsStrategy := storage.(rest.ResetFieldsStrategy); isResetFieldsStrategy {
|
||||
resetFields = resetFieldsStrategy.GetResetFields()
|
||||
}
|
||||
}
|
||||
|
||||
var versionedList interface{}
|
||||
if isLister {
|
||||
list := lister.NewList()
|
||||
listGVKs, _, err := a.group.Typer.ObjectKinds(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedListPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind(listGVKs[0].Kind))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedList = indirectArbitraryPointer(versionedListPtr)
|
||||
}
|
||||
|
||||
versionedListOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ListOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedCreateOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("CreateOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedPatchOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("PatchOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedUpdateOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("UpdateOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var versionedDeleteOptions runtime.Object
|
||||
@@ -295,7 +303,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isGracefulDeleter {
|
||||
versionedDeleteOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind("DeleteOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedDeleterObject = indirectArbitraryPointer(versionedDeleteOptions)
|
||||
|
||||
@@ -306,7 +314,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
|
||||
versionedStatusPtr, err := a.group.Creater.New(optionsExternalVersion.WithKind("Status"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedStatus := indirectArbitraryPointer(versionedStatusPtr)
|
||||
var (
|
||||
@@ -319,14 +327,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
getOptions, getSubpath, _ = getterWithOptions.NewGetOptions()
|
||||
getOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(getOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
getOptionsInternalKind = getOptionsInternalKinds[0]
|
||||
versionedGetOptions, err = a.group.Creater.New(a.group.GroupVersion.WithKind(getOptionsInternalKind.Kind))
|
||||
if err != nil {
|
||||
versionedGetOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(getOptionsInternalKind.Kind))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
isGetter = true
|
||||
@@ -336,7 +344,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isWatcher {
|
||||
versionedWatchEventPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind("WatchEvent"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
versionedWatchEvent = indirectArbitraryPointer(versionedWatchEventPtr)
|
||||
}
|
||||
@@ -352,7 +360,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if connectOptions != nil {
|
||||
connectOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(connectOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
connectOptionsInternalKind = connectOptionsInternalKinds[0]
|
||||
@@ -360,7 +368,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if err != nil {
|
||||
versionedConnectOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(connectOptionsInternalKind.Kind))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,7 +389,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
resourceKind = kind
|
||||
}
|
||||
|
||||
tableProvider, _ := storage.(rest.TableConvertor)
|
||||
tableProvider, isTableProvider := storage.(rest.TableConvertor)
|
||||
if isLister && !isTableProvider {
|
||||
// All listers must implement TableProvider
|
||||
return nil, nil, fmt.Errorf("%q must implement TableConvertor", resource)
|
||||
}
|
||||
|
||||
var apiResource metav1.APIResource
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionHash) &&
|
||||
@@ -390,7 +402,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
versioner := storageVersionProvider.StorageVersion()
|
||||
gvk, err := getStorageVersionKind(versioner, storage, a.group.Typer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
apiResource.StorageVersionHash = discovery.StorageVersionHash(gvk.Group, gvk.Version, gvk.Kind)
|
||||
}
|
||||
@@ -498,6 +510,36 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
}
|
||||
}
|
||||
|
||||
var resourceInfo *storageversion.ResourceInfo
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionAPI) &&
|
||||
utilfeature.DefaultFeatureGate.Enabled(features.APIServerIdentity) &&
|
||||
isStorageVersionProvider &&
|
||||
storageVersionProvider.StorageVersion() != nil {
|
||||
|
||||
versioner := storageVersionProvider.StorageVersion()
|
||||
encodingGVK, err := getStorageVersionKind(versioner, storage, a.group.Typer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
decodableVersions := []schema.GroupVersion{}
|
||||
if a.group.ConvertabilityChecker != nil {
|
||||
decodableVersions = a.group.ConvertabilityChecker.VersionsForGroupKind(fqKindToRegister.GroupKind())
|
||||
}
|
||||
resourceInfo = &storageversion.ResourceInfo{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: a.group.GroupVersion.Group,
|
||||
Resource: apiResource.Name,
|
||||
},
|
||||
EncodingVersion: encodingGVK.GroupVersion().String(),
|
||||
// We record EquivalentResourceMapper first instead of calculate
|
||||
// DecodableVersions immediately because API installation must
|
||||
// be completed first for us to know equivalent APIs
|
||||
EquivalentResourceMapper: a.group.EquivalentResourceRegistry,
|
||||
|
||||
DirectlyDecodableVersions: decodableVersions,
|
||||
}
|
||||
}
|
||||
|
||||
// Create Routes for the actions.
|
||||
// TODO: Add status documentation using Returns()
|
||||
// Errors (see api/errors/errors.go as well as go-restful router):
|
||||
@@ -517,7 +559,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
|
||||
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)
|
||||
return nil, 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)
|
||||
@@ -556,15 +598,17 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
}
|
||||
if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
reqScope.FieldManager, err = fieldmanager.NewDefaultFieldManager(
|
||||
a.group.OpenAPIModels,
|
||||
a.group.TypeConverter,
|
||||
a.group.UnsafeConvertor,
|
||||
a.group.Defaulter,
|
||||
a.group.Creater,
|
||||
fqKindToRegister,
|
||||
reqScope.HubGroupVersion,
|
||||
isSubresource,
|
||||
resetFields,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||
return nil, nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||
}
|
||||
}
|
||||
for _, action := range actions {
|
||||
@@ -585,6 +629,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
requestScope = "resource"
|
||||
operationSuffix = operationSuffix + "WithPath"
|
||||
}
|
||||
if strings.Index(action.Path, "/{name}") != -1 || action.Verb == "POST" {
|
||||
requestScope = "resource"
|
||||
}
|
||||
if action.AllNamespaces {
|
||||
requestScope = "cluster"
|
||||
operationSuffix = operationSuffix + "ForAllNamespaces"
|
||||
@@ -596,7 +643,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
kubeVerbs[kubeVerb] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown action verb for discovery: %s", action.Verb)
|
||||
return nil, nil, fmt.Errorf("unknown action verb for discovery: %s", action.Verb)
|
||||
}
|
||||
|
||||
routes := []*restful.RouteBuilder{}
|
||||
@@ -605,32 +652,55 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isSubresource {
|
||||
parentStorage, ok := a.group.Storage[resource]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing parent storage: %q", resource)
|
||||
return nil, nil, fmt.Errorf("missing parent storage: %q", resource)
|
||||
}
|
||||
|
||||
fqParentKind, err := GetResourceKind(a.group.GroupVersion, parentStorage, a.group.Typer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
kind = fqParentKind.Kind
|
||||
}
|
||||
|
||||
verbOverrider, needOverride := storage.(StorageMetricsOverride)
|
||||
|
||||
// accumulate endpoint-level warnings
|
||||
var (
|
||||
enableWarningHeaders = utilfeature.DefaultFeatureGate.Enabled(features.WarningHeaders)
|
||||
|
||||
warnings []string
|
||||
deprecated bool
|
||||
removedRelease string
|
||||
)
|
||||
|
||||
{
|
||||
versionedPtrWithGVK := versionedPtr.DeepCopyObject()
|
||||
versionedPtrWithGVK.GetObjectKind().SetGroupVersionKind(fqKindToRegister)
|
||||
currentMajor, currentMinor, _ := deprecation.MajorMinor(versioninfo.Get())
|
||||
deprecated = deprecation.IsDeprecated(versionedPtrWithGVK, currentMajor, currentMinor)
|
||||
if deprecated {
|
||||
removedRelease = deprecation.RemovedRelease(versionedPtrWithGVK)
|
||||
warnings = append(warnings, deprecation.WarningMessage(versionedPtrWithGVK))
|
||||
}
|
||||
}
|
||||
|
||||
switch action.Verb {
|
||||
case "GET": // Get a resource.
|
||||
var handler restful.RouteFunction
|
||||
if isGetterWithOptions {
|
||||
handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
|
||||
} else {
|
||||
handler = restfulGetResource(getter, exporter, reqScope)
|
||||
handler = restfulGetResource(getter, reqScope)
|
||||
}
|
||||
|
||||
if needOverride {
|
||||
// need change the reported verb
|
||||
handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler)
|
||||
handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler)
|
||||
} else {
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler)
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler)
|
||||
}
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
|
||||
doc := "read the specified " + kind
|
||||
@@ -646,12 +716,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Writes(producedObject)
|
||||
if isGetterWithOptions {
|
||||
if err := AddObjectParams(ws, route, versionedGetOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if isExporter {
|
||||
if err := AddObjectParams(ws, route, versionedExportOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
@@ -661,7 +726,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isSubresource {
|
||||
doc = "list " + subresource + " of objects of kind " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.GET(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
@@ -670,7 +738,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Returns(http.StatusOK, "OK", versionedList).
|
||||
Writes(versionedList)
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
switch {
|
||||
case isLister && isWatcher:
|
||||
@@ -693,7 +761,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isSubresource {
|
||||
doc = "replace " + subresource + " of the specified " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulUpdateResource(updater, reqScope, admit))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulUpdateResource(updater, reqScope, admit))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.PUT(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
@@ -706,7 +777,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Reads(defaultVersionedObject).
|
||||
Writes(producedObject)
|
||||
if err := AddObjectParams(ws, route, versionedUpdateOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
@@ -723,7 +794,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
supportedTypes = append(supportedTypes, string(types.ApplyPatchType))
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulPatchResource(patcher, reqScope, admit, supportedTypes))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulPatchResource(patcher, reqScope, admit, supportedTypes))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.PATCH(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
@@ -734,7 +808,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Reads(metav1.Patch{}).
|
||||
Writes(producedObject)
|
||||
if err := AddObjectParams(ws, route, versionedPatchOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
@@ -745,7 +819,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
} else {
|
||||
handler = restfulCreateResource(creater, reqScope, admit)
|
||||
}
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler)
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler)
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
article := GetArticleForNoun(kind, " ")
|
||||
doc := "create" + article + kind
|
||||
if isSubresource {
|
||||
@@ -764,7 +841,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Reads(defaultVersionedObject).
|
||||
Writes(producedObject)
|
||||
if err := AddObjectParams(ws, route, versionedCreateOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
@@ -778,7 +855,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if deleteReturnsDeletedObject {
|
||||
deleteReturnType = producedObject
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.DELETE(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
@@ -791,7 +871,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
route.Reads(versionedDeleterObject)
|
||||
route.ParameterNamed("body").Required(false)
|
||||
if err := AddObjectParams(ws, route, versionedDeleteOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
@@ -801,7 +881,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isSubresource {
|
||||
doc = "delete collection of " + subresource + " of a " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.DELETE(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
@@ -813,11 +896,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
route.Reads(versionedDeleterObject)
|
||||
route.ParameterNamed("body").Required(false)
|
||||
if err := AddObjectParams(ws, route, versionedDeleteOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
if err := AddObjectParams(ws, route, versionedListOptions, "watch", "allowWatchBookmarks"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
@@ -828,7 +911,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
doc = "watch changes to " + subresource + " of an object of kind " + kind
|
||||
}
|
||||
doc += ". deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter."
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.GET(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
@@ -837,7 +923,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Returns(http.StatusOK, "OK", versionedWatchEvent).
|
||||
Writes(versionedWatchEvent)
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
@@ -848,7 +934,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
doc = "watch individual changes to a list of " + subresource + " of " + kind
|
||||
}
|
||||
doc += ". deprecated: use the 'watch' parameter with a list operation instead."
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.GET(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
@@ -857,7 +946,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Returns(http.StatusOK, "OK", versionedWatchEvent).
|
||||
Writes(versionedWatchEvent)
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
@@ -871,7 +960,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if isSubresource {
|
||||
doc = "connect " + method + " requests to " + subresource + " of " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulConnectResource(connecter, reqScope, admit, path, isSubresource))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulConnectResource(connecter, reqScope, admit, path, isSubresource))
|
||||
if enableWarningHeaders {
|
||||
handler = utilwarning.AddWarningsHandler(handler, warnings)
|
||||
}
|
||||
route := ws.Method(method).Path(action.Path).
|
||||
To(handler).
|
||||
Doc(doc).
|
||||
@@ -881,7 +973,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Writes(connectProducedObject)
|
||||
if versionedConnectOptions != nil {
|
||||
if err := AddObjectParams(ws, route, versionedConnectOptions); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
@@ -895,7 +987,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized action verb: %s", action.Verb)
|
||||
return nil, nil, fmt.Errorf("unrecognized action verb: %s", action.Verb)
|
||||
}
|
||||
for _, route := range routes {
|
||||
route.Metadata(ROUTE_META_GVK, metav1.GroupVersionKind{
|
||||
@@ -931,7 +1023,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
// Record the existence of the GVR and the corresponding GVK
|
||||
a.group.EquivalentResourceRegistry.RegisterKindFor(reqScope.Resource, reqScope.Subresource, fqKindToRegister)
|
||||
|
||||
return &apiResource, nil
|
||||
return &apiResource, resourceInfo, nil
|
||||
}
|
||||
|
||||
// indirectArbitraryPointer returns *ptrToObject for an arbitrary pointer
|
||||
@@ -958,12 +1050,13 @@ func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
|
||||
// Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is
|
||||
// the JSON field name. If a description struct tag is set on the field, that description is used on the
|
||||
// query parameter. In essence, it converts a standard JSON top level object into a query param schema.
|
||||
func AddObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj interface{}) error {
|
||||
func AddObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj interface{}, excludedNames ...string) error {
|
||||
sv, err := conversion.EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st := sv.Type()
|
||||
excludedNameSet := sets.NewString(excludedNames...)
|
||||
switch st.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
@@ -989,7 +1082,9 @@ func AddObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj in
|
||||
if len(jsonName) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if excludedNameSet.Has(jsonName) {
|
||||
continue
|
||||
}
|
||||
var desc string
|
||||
if docable, ok := obj.(documentable); ok {
|
||||
desc = docable.SwaggerDoc()[jsonName]
|
||||
@@ -1017,6 +1112,10 @@ func typeToJSON(typeName string) string {
|
||||
return "string"
|
||||
case "v1.DeletionPropagation", "*v1.DeletionPropagation":
|
||||
return "string"
|
||||
case "v1.ResourceVersionMatch", "*v1.ResourceVersionMatch":
|
||||
return "string"
|
||||
case "v1.IncludeObjectPolicy", "*v1.IncludeObjectPolicy":
|
||||
return "string"
|
||||
|
||||
// TODO: Fix these when go-restful supports a way to specify an array query param:
|
||||
// https://github.com/emicklei/go-restful/issues/225
|
||||
@@ -1062,7 +1161,7 @@ func splitSubresource(path string) (string, string, error) {
|
||||
|
||||
// GetArticleForNoun returns the article needed for the given noun.
|
||||
func GetArticleForNoun(noun string, padding string) string {
|
||||
if noun[len(noun)-2:] != "ss" && noun[len(noun)-1:] == "s" {
|
||||
if !strings.HasSuffix(noun, "ss") && strings.HasSuffix(noun, "s") {
|
||||
// Plurals don't have an article.
|
||||
// Don't catch words like class
|
||||
return fmt.Sprintf("%v", padding)
|
||||
@@ -1129,9 +1228,9 @@ func restfulPatchResource(r rest.Patcher, scope handlers.RequestScope, admit adm
|
||||
}
|
||||
}
|
||||
|
||||
func restfulGetResource(r rest.Getter, e rest.Exporter, scope handlers.RequestScope) restful.RouteFunction {
|
||||
func restfulGetResource(r rest.Getter, scope handlers.RequestScope) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
handlers.GetResource(r, e, &scope)(res.ResponseWriter, req.Request)
|
||||
handlers.GetResource(r, &scope)(res.ResponseWriter, req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS
generated
vendored
3
vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS
generated
vendored
@@ -3,3 +3,6 @@
|
||||
reviewers:
|
||||
- wojtek-t
|
||||
- jimmidyson
|
||||
|
||||
approvers:
|
||||
- logicalhan
|
||||
|
||||
245
vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
generated
vendored
245
vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
generated
vendored
@@ -18,10 +18,10 @@ package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -31,6 +31,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilsets "k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
@@ -47,32 +49,36 @@ type resettableCollector interface {
|
||||
|
||||
const (
|
||||
APIServerComponent string = "apiserver"
|
||||
OtherContentType string = "other"
|
||||
OtherRequestMethod string = "other"
|
||||
)
|
||||
|
||||
/*
|
||||
* By default, all the following metrics are defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
* the metric stability policy.
|
||||
*/
|
||||
var (
|
||||
deprecatedRequestGauge = compbasemetrics.NewGaugeVec(
|
||||
&compbasemetrics.GaugeOpts{
|
||||
Name: "apiserver_requested_deprecated_apis",
|
||||
Help: "Gauge of deprecated APIs that have been requested, broken out by API group, version, resource, subresource, and removed_release.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"group", "version", "resource", "subresource", "removed_release"},
|
||||
)
|
||||
|
||||
// TODO(a-robinson): Add unit tests for the handling of these metrics once
|
||||
// the upstream library supports it.
|
||||
requestCounter = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_request_total",
|
||||
Help: "Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response contentType and code.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
Help: "Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, and HTTP response code.",
|
||||
StabilityLevel: compbasemetrics.STABLE,
|
||||
},
|
||||
// The label_name contentType doesn't follow the label_name convention defined here:
|
||||
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/instrumentation.md
|
||||
// But changing it would break backwards compatibility. Future label_names
|
||||
// should be all lowercase and separated by underscores.
|
||||
[]string{"verb", "dry_run", "group", "version", "resource", "subresource", "scope", "component", "contentType", "code"},
|
||||
[]string{"verb", "dry_run", "group", "version", "resource", "subresource", "scope", "component", "code"},
|
||||
)
|
||||
longRunningRequestGauge = compbasemetrics.NewGaugeVec(
|
||||
&compbasemetrics.GaugeOpts{
|
||||
@@ -91,7 +97,7 @@ var (
|
||||
// Thus we customize buckets significantly, to empower both usecases.
|
||||
Buckets: []float64{0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
|
||||
1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40, 50, 60},
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
StabilityLevel: compbasemetrics.STABLE,
|
||||
},
|
||||
[]string{"verb", "dry_run", "group", "version", "resource", "subresource", "scope", "component"},
|
||||
)
|
||||
@@ -112,7 +118,15 @@ var (
|
||||
Help: "Number of requests dropped with 'Try again later' response",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"requestKind"},
|
||||
[]string{"request_kind"},
|
||||
)
|
||||
// TLSHandshakeErrors is a number of requests dropped with 'TLS handshake error from' error
|
||||
TLSHandshakeErrors = compbasemetrics.NewCounter(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_tls_handshake_errors_total",
|
||||
Help: "Number of requests dropped with 'TLS handshake error from' error",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
)
|
||||
// RegisteredWatchers is a number of currently registered watchers splitted by resource.
|
||||
RegisteredWatchers = compbasemetrics.NewGaugeVec(
|
||||
@@ -148,7 +162,15 @@ var (
|
||||
Help: "Maximal number of currently used inflight request limit of this apiserver per request kind in last second.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"requestKind"},
|
||||
[]string{"request_kind"},
|
||||
)
|
||||
currentInqueueRequests = compbasemetrics.NewGaugeVec(
|
||||
&compbasemetrics.GaugeOpts{
|
||||
Name: "apiserver_current_inqueue_requests",
|
||||
Help: "Maximal number of queued requests in this apiserver per request kind in last second.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"request_kind"},
|
||||
)
|
||||
|
||||
requestTerminationsTotal = compbasemetrics.NewCounterVec(
|
||||
@@ -159,34 +181,55 @@ var (
|
||||
},
|
||||
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component", "code"},
|
||||
)
|
||||
kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`)
|
||||
|
||||
apiSelfRequestCounter = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_selfrequest_total",
|
||||
Help: "Counter of apiserver self-requests broken out for each verb, API resource and subresource.",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"verb", "resource", "subresource"},
|
||||
)
|
||||
|
||||
requestFilterDuration = compbasemetrics.NewHistogramVec(
|
||||
&compbasemetrics.HistogramOpts{
|
||||
Name: "apiserver_request_filter_duration_seconds",
|
||||
Help: "Request filter latency distribution in seconds, for each filter type",
|
||||
Buckets: []float64{0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1.0, 5.0},
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"filter"},
|
||||
)
|
||||
|
||||
// requestAbortsTotal is a number of aborted requests with http.ErrAbortHandler
|
||||
requestAbortsTotal = compbasemetrics.NewCounterVec(
|
||||
&compbasemetrics.CounterOpts{
|
||||
Name: "apiserver_request_aborts_total",
|
||||
Help: "Number of requests which apiserver aborted possibly due to a timeout, for each group, version, verb, resource, subresource and scope",
|
||||
StabilityLevel: compbasemetrics.ALPHA,
|
||||
},
|
||||
[]string{"verb", "group", "version", "resource", "subresource", "scope"},
|
||||
)
|
||||
|
||||
metrics = []resettableCollector{
|
||||
deprecatedRequestGauge,
|
||||
requestCounter,
|
||||
longRunningRequestGauge,
|
||||
requestLatencies,
|
||||
responseSizes,
|
||||
DroppedRequests,
|
||||
TLSHandshakeErrors,
|
||||
RegisteredWatchers,
|
||||
WatchEvents,
|
||||
WatchEventsSizes,
|
||||
currentInflightRequests,
|
||||
currentInqueueRequests,
|
||||
requestTerminationsTotal,
|
||||
apiSelfRequestCounter,
|
||||
requestFilterDuration,
|
||||
requestAbortsTotal,
|
||||
}
|
||||
|
||||
// these are the known (e.g. whitelisted/known) content types which we will report for
|
||||
// request metrics. Any other RFC compliant content types will be aggregated under 'unknown'
|
||||
knownMetricContentTypes = utilsets.NewString(
|
||||
"application/apply-patch+yaml",
|
||||
"application/json",
|
||||
"application/json-patch+json",
|
||||
"application/merge-patch+json",
|
||||
"application/strategic-merge-patch+json",
|
||||
"application/vnd.kubernetes.protobuf",
|
||||
"application/vnd.kubernetes.protobuf;stream=watch",
|
||||
"application/yaml",
|
||||
"text/plain",
|
||||
"text/plain;charset=utf-8")
|
||||
// these are the valid request methods which we report in our metrics. Any other request methods
|
||||
// will be aggregated under 'unknown'
|
||||
validRequestMethods = utilsets.NewString(
|
||||
@@ -211,6 +254,21 @@ const (
|
||||
ReadOnlyKind = "readOnly"
|
||||
// MutatingKind is a string identifying mutating request kind
|
||||
MutatingKind = "mutating"
|
||||
|
||||
// WaitingPhase is the phase value for a request waiting in a queue
|
||||
WaitingPhase = "waiting"
|
||||
// ExecutingPhase is the phase value for an executing request
|
||||
ExecutingPhase = "executing"
|
||||
)
|
||||
|
||||
const (
|
||||
// deprecatedAnnotationKey is a key for an audit annotation set to
|
||||
// "true" on requests made to deprecated API versions
|
||||
deprecatedAnnotationKey = "k8s.io/deprecated"
|
||||
// removedReleaseAnnotationKey is a key for an audit annotation set to
|
||||
// the target removal release, in "<major>.<minor>" format,
|
||||
// on requests made to deprecated API versions with a target removal release
|
||||
removedReleaseAnnotationKey = "k8s.io/removed-release"
|
||||
)
|
||||
|
||||
var registerMetrics sync.Once
|
||||
@@ -231,9 +289,39 @@ func Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateInflightRequestMetrics(nonmutating, mutating int) {
|
||||
currentInflightRequests.WithLabelValues(ReadOnlyKind).Set(float64(nonmutating))
|
||||
currentInflightRequests.WithLabelValues(MutatingKind).Set(float64(mutating))
|
||||
// UpdateInflightRequestMetrics reports concurrency metrics classified by
|
||||
// mutating vs Readonly.
|
||||
func UpdateInflightRequestMetrics(phase string, nonmutating, mutating int) {
|
||||
for _, kc := range []struct {
|
||||
kind string
|
||||
count int
|
||||
}{{ReadOnlyKind, nonmutating}, {MutatingKind, mutating}} {
|
||||
if phase == ExecutingPhase {
|
||||
currentInflightRequests.WithLabelValues(kc.kind).Set(float64(kc.count))
|
||||
} else {
|
||||
currentInqueueRequests.WithLabelValues(kc.kind).Set(float64(kc.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RecordFilterLatency(ctx context.Context, name string, elapsed time.Duration) {
|
||||
requestFilterDuration.WithContext(ctx).WithLabelValues(name).Observe(elapsed.Seconds())
|
||||
}
|
||||
|
||||
// RecordRequestAbort records that the request was aborted possibly due to a timeout.
|
||||
func RecordRequestAbort(req *http.Request, requestInfo *request.RequestInfo) {
|
||||
if requestInfo == nil {
|
||||
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||
}
|
||||
|
||||
scope := CleanScope(requestInfo)
|
||||
reportedVerb := cleanVerb(canonicalVerb(strings.ToUpper(req.Method), scope), req)
|
||||
resource := requestInfo.Resource
|
||||
subresource := requestInfo.Subresource
|
||||
group := requestInfo.APIGroup
|
||||
version := requestInfo.APIVersion
|
||||
|
||||
requestAbortsTotal.WithContext(req.Context()).WithLabelValues(reportedVerb, group, version, resource, subresource, scope).Inc()
|
||||
}
|
||||
|
||||
// RecordRequestTermination records that the request was terminated early as part of a resource
|
||||
@@ -245,20 +333,17 @@ func RecordRequestTermination(req *http.Request, requestInfo *request.RequestInf
|
||||
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||
}
|
||||
scope := CleanScope(requestInfo)
|
||||
// We don't use verb from <requestInfo>, as for the healthy path
|
||||
// MonitorRequest is called from InstrumentRouteFunc which is registered
|
||||
// in installer.go with predefined list of verbs (different than those
|
||||
// translated to RequestInfo).
|
||||
|
||||
// We don't use verb from <requestInfo>, as this may be propagated from
|
||||
// InstrumentRouteFunc which is registered in installer.go with predefined
|
||||
// list of verbs (different than those translated to RequestInfo).
|
||||
// However, we need to tweak it e.g. to differentiate GET from LIST.
|
||||
verb := canonicalVerb(strings.ToUpper(req.Method), scope)
|
||||
// set verbs to a bounded set of known and expected verbs
|
||||
if !validRequestMethods.Has(verb) {
|
||||
verb = OtherRequestMethod
|
||||
}
|
||||
reportedVerb := cleanVerb(canonicalVerb(strings.ToUpper(req.Method), scope), req)
|
||||
|
||||
if requestInfo.IsResourceRequest {
|
||||
requestTerminationsTotal.WithLabelValues(cleanVerb(verb, req), requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component, codeToString(code)).Inc()
|
||||
requestTerminationsTotal.WithContext(req.Context()).WithLabelValues(reportedVerb, requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component, codeToString(code)).Inc()
|
||||
} else {
|
||||
requestTerminationsTotal.WithLabelValues(cleanVerb(verb, req), "", "", "", requestInfo.Path, scope, component, codeToString(code)).Inc()
|
||||
requestTerminationsTotal.WithContext(req.Context()).WithLabelValues(reportedVerb, "", "", "", requestInfo.Path, scope, component, codeToString(code)).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,16 +355,17 @@ func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, comp
|
||||
}
|
||||
var g compbasemetrics.GaugeMetric
|
||||
scope := CleanScope(requestInfo)
|
||||
// We don't use verb from <requestInfo>, as for the healthy path
|
||||
// MonitorRequest is called from InstrumentRouteFunc which is registered
|
||||
// in installer.go with predefined list of verbs (different than those
|
||||
// translated to RequestInfo).
|
||||
|
||||
// We don't use verb from <requestInfo>, as this may be propagated from
|
||||
// InstrumentRouteFunc which is registered in installer.go with predefined
|
||||
// list of verbs (different than those translated to RequestInfo).
|
||||
// However, we need to tweak it e.g. to differentiate GET from LIST.
|
||||
reportedVerb := cleanVerb(canonicalVerb(strings.ToUpper(req.Method), scope), req)
|
||||
|
||||
if requestInfo.IsResourceRequest {
|
||||
g = longRunningRequestGauge.WithLabelValues(reportedVerb, requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component)
|
||||
g = longRunningRequestGauge.WithContext(req.Context()).WithLabelValues(reportedVerb, requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component)
|
||||
} else {
|
||||
g = longRunningRequestGauge.WithLabelValues(reportedVerb, "", "", "", requestInfo.Path, scope, component)
|
||||
g = longRunningRequestGauge.WithContext(req.Context()).WithLabelValues(reportedVerb, "", "", "", requestInfo.Path, scope, component)
|
||||
}
|
||||
g.Inc()
|
||||
defer g.Dec()
|
||||
@@ -288,24 +374,43 @@ func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, comp
|
||||
|
||||
// MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record
|
||||
// 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)
|
||||
func MonitorRequest(req *http.Request, verb, group, version, resource, subresource, scope, component string, deprecated bool, removedRelease string, httpCode, respSize int, elapsed time.Duration) {
|
||||
// We don't use verb from <requestInfo>, as this may be propagated from
|
||||
// InstrumentRouteFunc which is registered in installer.go with predefined
|
||||
// list of verbs (different than those translated to RequestInfo).
|
||||
// However, we need to tweak it e.g. to differentiate GET from LIST.
|
||||
reportedVerb := cleanVerb(canonicalVerb(strings.ToUpper(req.Method), scope), req)
|
||||
|
||||
dryRun := cleanDryRun(req.URL)
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
cleanContentType := cleanContentType(contentType)
|
||||
requestCounter.WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component, cleanContentType, codeToString(httpCode)).Inc()
|
||||
requestLatencies.WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component).Observe(elapsedSeconds)
|
||||
requestCounter.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component, codeToString(httpCode)).Inc()
|
||||
// MonitorRequest happens after authentication, so we can trust the username given by the request
|
||||
info, ok := request.UserFrom(req.Context())
|
||||
if ok && info.GetName() == user.APIServerUser {
|
||||
apiSelfRequestCounter.WithContext(req.Context()).WithLabelValues(reportedVerb, resource, subresource).Inc()
|
||||
}
|
||||
if deprecated {
|
||||
deprecatedRequestGauge.WithContext(req.Context()).WithLabelValues(group, version, resource, subresource, removedRelease).Set(1)
|
||||
audit.AddAuditAnnotation(req.Context(), deprecatedAnnotationKey, "true")
|
||||
if len(removedRelease) > 0 {
|
||||
audit.AddAuditAnnotation(req.Context(), removedReleaseAnnotationKey, removedRelease)
|
||||
}
|
||||
}
|
||||
requestLatencies.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component).Observe(elapsedSeconds)
|
||||
// We are only interested in response sizes of read requests.
|
||||
if verb == "GET" || verb == "LIST" {
|
||||
responseSizes.WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(float64(respSize))
|
||||
responseSizes.WithContext(req.Context()).WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(float64(respSize))
|
||||
}
|
||||
}
|
||||
|
||||
// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
|
||||
// the go-restful RouteFunction instead of a HandlerFunc plus some Kubernetes endpoint specific information.
|
||||
func InstrumentRouteFunc(verb, group, version, resource, subresource, scope, component string, routeFunc restful.RouteFunction) restful.RouteFunction {
|
||||
return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
|
||||
now := time.Now()
|
||||
func InstrumentRouteFunc(verb, group, version, resource, subresource, scope, component string, deprecated bool, removedRelease string, routeFunc restful.RouteFunction) restful.RouteFunction {
|
||||
return restful.RouteFunction(func(req *restful.Request, response *restful.Response) {
|
||||
requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(req.Request.Context())
|
||||
if !ok {
|
||||
requestReceivedTimestamp = time.Now()
|
||||
}
|
||||
|
||||
delegate := &ResponseWriterDelegator{ResponseWriter: response.ResponseWriter}
|
||||
|
||||
@@ -320,16 +425,19 @@ func InstrumentRouteFunc(verb, group, version, resource, subresource, scope, com
|
||||
}
|
||||
response.ResponseWriter = rw
|
||||
|
||||
routeFunc(request, response)
|
||||
routeFunc(req, response)
|
||||
|
||||
MonitorRequest(request.Request, verb, group, version, resource, subresource, scope, component, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
|
||||
MonitorRequest(req.Request, verb, group, version, resource, subresource, scope, component, deprecated, removedRelease, delegate.Status(), delegate.ContentLength(), time.Since(requestReceivedTimestamp))
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentHandlerFunc works like Prometheus' InstrumentHandlerFunc but adds some Kubernetes endpoint specific information.
|
||||
func InstrumentHandlerFunc(verb, group, version, resource, subresource, scope, component string, handler http.HandlerFunc) http.HandlerFunc {
|
||||
func InstrumentHandlerFunc(verb, group, version, resource, subresource, scope, component string, deprecated bool, removedRelease string, handler http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
now := time.Now()
|
||||
requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(req.Context())
|
||||
if !ok {
|
||||
requestReceivedTimestamp = time.Now()
|
||||
}
|
||||
|
||||
delegate := &ResponseWriterDelegator{ResponseWriter: w}
|
||||
|
||||
@@ -344,23 +452,10 @@ func InstrumentHandlerFunc(verb, group, version, resource, subresource, scope, c
|
||||
|
||||
handler(w, req)
|
||||
|
||||
MonitorRequest(req, verb, group, version, resource, subresource, scope, component, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
|
||||
MonitorRequest(req, verb, group, version, resource, subresource, scope, component, deprecated, removedRelease, delegate.Status(), delegate.ContentLength(), time.Since(requestReceivedTimestamp))
|
||||
}
|
||||
}
|
||||
|
||||
// cleanContentType binds the contentType (for metrics related purposes) to a
|
||||
// bounded set of known/expected content-types.
|
||||
func cleanContentType(contentType string) string {
|
||||
normalizedContentType := strings.ToLower(contentType)
|
||||
if strings.HasSuffix(contentType, " stream=watch") || strings.HasSuffix(contentType, " charset=utf-8") {
|
||||
normalizedContentType = strings.ReplaceAll(contentType, " ", "")
|
||||
}
|
||||
if knownMetricContentTypes.Has(normalizedContentType) {
|
||||
return normalizedContentType
|
||||
}
|
||||
return OtherContentType
|
||||
}
|
||||
|
||||
// CleanScope returns the scope of the request.
|
||||
func CleanScope(requestInfo *request.RequestInfo) string {
|
||||
if requestInfo.Namespace != "" {
|
||||
@@ -379,7 +474,7 @@ func CleanScope(requestInfo *request.RequestInfo) string {
|
||||
func canonicalVerb(verb string, scope string) string {
|
||||
switch verb {
|
||||
case "GET", "HEAD":
|
||||
if scope != "resource" {
|
||||
if scope != "resource" && scope != "" {
|
||||
return "LIST"
|
||||
}
|
||||
return "GET"
|
||||
|
||||
4
vendor/k8s.io/apiserver/pkg/endpoints/openapi/OWNERS
generated
vendored
4
vendor/k8s.io/apiserver/pkg/endpoints/openapi/OWNERS
generated
vendored
@@ -1,4 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- mbohlool
|
||||
3
vendor/k8s.io/apiserver/pkg/endpoints/request/context.go
generated
vendored
3
vendor/k8s.io/apiserver/pkg/endpoints/request/context.go
generated
vendored
@@ -36,9 +36,6 @@ const (
|
||||
|
||||
// auditKey is the context key for the audit event.
|
||||
auditKey
|
||||
|
||||
// audiencesKey is the context key for request audiences.
|
||||
audiencesKey
|
||||
)
|
||||
|
||||
// NewContext instantiates a base context object for request flows.
|
||||
|
||||
45
vendor/k8s.io/apiserver/pkg/endpoints/request/received_time.go
generated
vendored
Normal file
45
vendor/k8s.io/apiserver/pkg/endpoints/request/received_time.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type requestReceivedTimestampKeyType int
|
||||
|
||||
// requestReceivedTimestampKey is the ReceivedTimestamp (the time the request reached the apiserver)
|
||||
// key for the context.
|
||||
const requestReceivedTimestampKey requestReceivedTimestampKeyType = iota
|
||||
|
||||
// WithReceivedTimestamp returns a copy of parent context in which the ReceivedTimestamp
|
||||
// (the time the request reached the apiserver) is set.
|
||||
//
|
||||
// If the specified ReceivedTimestamp is zero, no value is set and the parent context is returned as is.
|
||||
func WithReceivedTimestamp(parent context.Context, receivedTimestamp time.Time) context.Context {
|
||||
if receivedTimestamp.IsZero() {
|
||||
return parent
|
||||
}
|
||||
return WithValue(parent, requestReceivedTimestampKey, receivedTimestamp)
|
||||
}
|
||||
|
||||
// ReceivedTimestampFrom returns the value of the ReceivedTimestamp key from the specified context.
|
||||
func ReceivedTimestampFrom(ctx context.Context) (time.Time, bool) {
|
||||
info, ok := ctx.Value(requestReceivedTimestampKey).(time.Time)
|
||||
return info, ok
|
||||
}
|
||||
6
vendor/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go
generated
vendored
6
vendor/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go
generated
vendored
@@ -28,7 +28,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// LongRunningRequestCheck is a predicate which is true for long-running http requests.
|
||||
@@ -77,7 +77,7 @@ var specialVerbsNoSubresources = sets.NewString("proxy")
|
||||
// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
|
||||
var namespaceSubresources = sets.NewString("status", "finalize")
|
||||
|
||||
// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift
|
||||
// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/controlplane/master_test.go, so we never drift
|
||||
var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
|
||||
|
||||
type RequestInfoFactory struct {
|
||||
@@ -211,7 +211,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
||||
opts := metainternalversion.ListOptions{}
|
||||
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil {
|
||||
// An error in parsing request will result in default to "list" and not setting "name" field.
|
||||
klog.Errorf("Couldn't parse request %#v: %v", req.URL.Query(), err)
|
||||
klog.ErrorS(err, "Couldn't parse request", "Request", req.URL.Query())
|
||||
// Reset opts to not rely on partial results from parsing.
|
||||
// However, if watch is set, let's report it.
|
||||
opts = metainternalversion.ListOptions{}
|
||||
|
||||
39
vendor/k8s.io/apiserver/pkg/endpoints/warning/warning.go
generated
vendored
Normal file
39
vendor/k8s.io/apiserver/pkg/endpoints/warning/warning.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package warning
|
||||
|
||||
import (
|
||||
restful "github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
)
|
||||
|
||||
// AddWarningsHandler returns a handler that adds the provided warnings to all requests,
|
||||
// then delegates to the provided handler.
|
||||
func AddWarningsHandler(handler restful.RouteFunction, warnings []string) restful.RouteFunction {
|
||||
if len(warnings) == 0 {
|
||||
return handler
|
||||
}
|
||||
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
ctx := req.Request.Context()
|
||||
for _, msg := range warnings {
|
||||
warning.AddWarning(ctx, "", msg)
|
||||
}
|
||||
handler(req, res)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user