mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-07 10:17:51 +00:00
vendor dependencies
This commit is contained in:
parent
604208ef4f
commit
72abf135d6
1156 changed files with 78178 additions and 105799 deletions
18
vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
generated
vendored
18
vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
generated
vendored
|
|
@ -32,23 +32,21 @@ import (
|
|||
// legacyRootAPIHandler creates a webservice serving api group discovery.
|
||||
type legacyRootAPIHandler struct {
|
||||
// addresses is used to build cluster IPs for discovery.
|
||||
addresses Addresses
|
||||
apiPrefix string
|
||||
serializer runtime.NegotiatedSerializer
|
||||
apiVersions []string
|
||||
addresses Addresses
|
||||
apiPrefix string
|
||||
serializer runtime.NegotiatedSerializer
|
||||
}
|
||||
|
||||
func NewLegacyRootAPIHandler(addresses Addresses, serializer runtime.NegotiatedSerializer, apiPrefix string, apiVersions []string) *legacyRootAPIHandler {
|
||||
func NewLegacyRootAPIHandler(addresses Addresses, serializer runtime.NegotiatedSerializer, apiPrefix string) *legacyRootAPIHandler {
|
||||
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
||||
// use stripVersionNegotiatedSerializer to keep the response backwards
|
||||
// compatible.
|
||||
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||
|
||||
return &legacyRootAPIHandler{
|
||||
addresses: addresses,
|
||||
apiPrefix: apiPrefix,
|
||||
serializer: serializer,
|
||||
apiVersions: apiVersions,
|
||||
addresses: addresses,
|
||||
apiPrefix: apiPrefix,
|
||||
serializer: serializer,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +69,7 @@ func (s *legacyRootAPIHandler) handle(req *restful.Request, resp *restful.Respon
|
|||
clientIP := utilnet.GetClientIP(req.Request)
|
||||
apiVersions := &metav1.APIVersions{
|
||||
ServerAddressByClientCIDRs: s.addresses.ServerAddressByClientCIDRs(clientIP),
|
||||
Versions: s.apiVersions,
|
||||
Versions: []string{"v1"},
|
||||
}
|
||||
|
||||
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, apiVersions)
|
||||
|
|
|
|||
40
vendor/k8s.io/apiserver/pkg/endpoints/discovery/storageversionhash.go
generated
vendored
Normal file
40
vendor/k8s.io/apiserver/pkg/endpoints/discovery/storageversionhash.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// StorageVersionHash calculates the storage version hash for a
|
||||
// <group/version/kind> tuple.
|
||||
// WARNING: this function is subject to change. Clients shouldn't depend on
|
||||
// this function.
|
||||
func StorageVersionHash(group, version, kind string) string {
|
||||
gvk := group + "/" + version + "/" + kind
|
||||
if gvk == "" {
|
||||
return ""
|
||||
}
|
||||
bytes := sha256.Sum256([]byte(gvk))
|
||||
// Assuming there are N kinds in the cluster, and the hash is X-byte long,
|
||||
// the chance of colliding hash P(N,X) approximates to 1-e^(-(N^2)/2^(8X+1)).
|
||||
// P(10,000, 8) ~= 2.7*10^(-12), which is low enough.
|
||||
// See https://en.wikipedia.org/wiki/Birthday_problem#Approximations.
|
||||
return base64.StdEncoding.EncodeToString(
|
||||
bytes[:8])
|
||||
}
|
||||
2
vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
generated
vendored
Executable file → Normal file
2
vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
generated
vendored
Executable file → Normal file
|
|
@ -1,3 +1,5 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- deads2k
|
||||
- sttts
|
||||
|
|
|
|||
12
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
generated
vendored
12
vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
generated
vendored
|
|
@ -56,7 +56,11 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker, lon
|
|||
}
|
||||
|
||||
ev.Stage = auditinternal.StageRequestReceived
|
||||
processAuditEvent(sink, ev, omitStages)
|
||||
if processed := processAuditEvent(sink, ev, omitStages); !processed {
|
||||
audit.ApiserverAuditDroppedCounter.Inc()
|
||||
responsewriters.InternalError(w, req, errors.New("failed to store audit event"))
|
||||
return
|
||||
}
|
||||
|
||||
// intercept the status code
|
||||
var longRunningSink audit.Sink
|
||||
|
|
@ -137,10 +141,10 @@ func createAuditEventAndAttachToContext(req *http.Request, policy policy.Checker
|
|||
return req, ev, omitStages, nil
|
||||
}
|
||||
|
||||
func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) {
|
||||
func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) bool {
|
||||
for _, stage := range omitStages {
|
||||
if ev.Stage == stage {
|
||||
return
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +154,7 @@ func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []au
|
|||
ev.StageTimestamp = metav1.NewMicroTime(time.Now())
|
||||
}
|
||||
audit.ObserveEvent()
|
||||
sink.ProcessEvents(ev)
|
||||
return sink.ProcessEvents(ev)
|
||||
}
|
||||
|
||||
func decorateResponseWriter(responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter {
|
||||
|
|
|
|||
20
vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
generated
vendored
20
vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
generated
vendored
|
|
@ -21,8 +21,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"k8s.io/klog"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
|
@ -50,27 +50,33 @@ func init() {
|
|||
// 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) http.Handler {
|
||||
func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences) http.Handler {
|
||||
if auth == nil {
|
||||
glog.Warningf("Authentication is disabled")
|
||||
klog.Warningf("Authentication is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
user, ok, err := auth.AuthenticateRequest(req)
|
||||
if len(apiAuds) > 0 {
|
||||
req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
|
||||
}
|
||||
resp, ok, err := auth.AuthenticateRequest(req)
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
glog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
||||
klog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
||||
}
|
||||
failed.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(mikedanese): verify the response audience matches one of apiAuds if
|
||||
// non-empty
|
||||
|
||||
// authorization header is not required anymore in case of a successful authentication.
|
||||
req.Header.Del("Authorization")
|
||||
|
||||
req = req.WithContext(genericapirequest.WithUser(req.Context(), user))
|
||||
req = req.WithContext(genericapirequest.WithUser(req.Context(), resp.User))
|
||||
|
||||
authenticatedUserCounter.WithLabelValues(compressUsername(user.GetName())).Inc()
|
||||
authenticatedUserCounter.WithLabelValues(compressUsername(resp.User.GetName())).Inc()
|
||||
|
||||
handler.ServeHTTP(w, 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"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
|
||||
"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 {
|
||||
glog.Warningf("Authorization is disabled")
|
||||
klog.Warningf("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
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
|
||||
klog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
|
||||
audit.LogAnnotation(ae, decisionAnnotationKey, decisionForbid)
|
||||
audit.LogAnnotation(ae, reasonAnnotationKey, reason)
|
||||
responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
|
||||
|
|
|
|||
8
vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
generated
vendored
|
|
@ -23,7 +23,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
|
|
@ -42,7 +42,7 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
impersonationRequests, err := buildImpersonationRequests(req.Header)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("%v", err)
|
||||
klog.V(4).Infof("%v", err)
|
||||
responsewriters.InternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
|
@ -102,14 +102,14 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.
|
|||
userExtra[extraKey] = append(userExtra[extraKey], extraValue)
|
||||
|
||||
default:
|
||||
glog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest)
|
||||
klog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest)
|
||||
responsewriters.Forbidden(ctx, actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest), s)
|
||||
return
|
||||
}
|
||||
|
||||
decision, reason, err := a.Authorize(actingAsAttributes)
|
||||
if err != nil || decision != authorizer.DecisionAllow {
|
||||
glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err)
|
||||
klog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err)
|
||||
responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
161
vendor/k8s.io/apiserver/pkg/endpoints/filters/legacy_audit.go
generated
vendored
161
vendor/k8s.io/apiserver/pkg/endpoints/filters/legacy_audit.go
generated
vendored
|
|
@ -1,161 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 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 (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
authenticationapi "k8s.io/api/authentication/v1"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
)
|
||||
|
||||
var _ http.ResponseWriter = &legacyAuditResponseWriter{}
|
||||
|
||||
type legacyAuditResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
out io.Writer
|
||||
id string
|
||||
}
|
||||
|
||||
func (a *legacyAuditResponseWriter) printResponse(code int) {
|
||||
line := fmt.Sprintf("%s AUDIT: id=%q response=\"%d\"\n", time.Now().Format(time.RFC3339Nano), a.id, code)
|
||||
if _, err := fmt.Fprint(a.out, line); err != nil {
|
||||
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *legacyAuditResponseWriter) WriteHeader(code int) {
|
||||
a.printResponse(code)
|
||||
a.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// fancyLegacyResponseWriterDelegator implements http.CloseNotifier, http.Flusher and
|
||||
// http.Hijacker which are needed to make certain http operation (e.g. watch, rsh, etc)
|
||||
// working.
|
||||
type fancyLegacyResponseWriterDelegator struct {
|
||||
*legacyAuditResponseWriter
|
||||
}
|
||||
|
||||
func (f *fancyLegacyResponseWriterDelegator) CloseNotify() <-chan bool {
|
||||
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (f *fancyLegacyResponseWriterDelegator) Flush() {
|
||||
f.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (f *fancyLegacyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
// fake a response status before protocol switch happens
|
||||
f.printResponse(http.StatusSwitchingProtocols)
|
||||
return f.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
var _ http.CloseNotifier = &fancyLegacyResponseWriterDelegator{}
|
||||
var _ http.Flusher = &fancyLegacyResponseWriterDelegator{}
|
||||
var _ http.Hijacker = &fancyLegacyResponseWriterDelegator{}
|
||||
|
||||
// WithLegacyAudit decorates a http.Handler with audit logging information for all the
|
||||
// requests coming to the server. If out is nil, no decoration takes place.
|
||||
// Each audit log contains two entries:
|
||||
// 1. the request line containing:
|
||||
// - unique id allowing to match the response line (see 2)
|
||||
// - source ip of the request
|
||||
// - HTTP method being invoked
|
||||
// - original user invoking the operation
|
||||
// - original user's groups info
|
||||
// - impersonated user for the operation
|
||||
// - impersonated groups info
|
||||
// - namespace of the request or <none>
|
||||
// - uri is the full URI as requested
|
||||
// 2. the response line containing:
|
||||
// - the unique id from 1
|
||||
// - response code
|
||||
func WithLegacyAudit(handler http.Handler, out io.Writer) http.Handler {
|
||||
if out == nil {
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
attribs, err := GetAuthorizerAttributes(ctx)
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
username := "<none>"
|
||||
groups := "<none>"
|
||||
if attribs.GetUser() != nil {
|
||||
username = attribs.GetUser().GetName()
|
||||
if userGroups := attribs.GetUser().GetGroups(); len(userGroups) > 0 {
|
||||
groups = auditStringSlice(userGroups)
|
||||
}
|
||||
}
|
||||
asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader)
|
||||
if len(asuser) == 0 {
|
||||
asuser = "<self>"
|
||||
}
|
||||
asgroups := "<lookup>"
|
||||
requestedGroups := req.Header[authenticationapi.ImpersonateGroupHeader]
|
||||
if len(requestedGroups) > 0 {
|
||||
asgroups = auditStringSlice(requestedGroups)
|
||||
}
|
||||
namespace := attribs.GetNamespace()
|
||||
if len(namespace) == 0 {
|
||||
namespace = "<none>"
|
||||
}
|
||||
id := uuid.NewRandom().String()
|
||||
|
||||
line := fmt.Sprintf("%s AUDIT: id=%q ip=%q method=%q user=%q groups=%q as=%q asgroups=%q namespace=%q uri=%q\n",
|
||||
time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, username, groups, asuser, asgroups, namespace, req.URL)
|
||||
if _, err := fmt.Fprint(out, line); err != nil {
|
||||
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
|
||||
}
|
||||
respWriter := legacyDecorateResponseWriter(w, out, id)
|
||||
handler.ServeHTTP(respWriter, req)
|
||||
})
|
||||
}
|
||||
|
||||
func auditStringSlice(inList []string) string {
|
||||
quotedElements := make([]string, len(inList))
|
||||
for i, in := range inList {
|
||||
quotedElements[i] = fmt.Sprintf("%q", in)
|
||||
}
|
||||
return strings.Join(quotedElements, ",")
|
||||
}
|
||||
|
||||
func legacyDecorateResponseWriter(responseWriter http.ResponseWriter, out io.Writer, id string) http.ResponseWriter {
|
||||
delegate := &legacyAuditResponseWriter{ResponseWriter: responseWriter, out: out, id: id}
|
||||
// check if the ResponseWriter we're wrapping is the fancy one we need
|
||||
// or if the basic is sufficient
|
||||
_, cn := responseWriter.(http.CloseNotifier)
|
||||
_, fl := responseWriter.(http.Flusher)
|
||||
_, hj := responseWriter.(http.Hijacker)
|
||||
if cn && fl && hj {
|
||||
return &fancyLegacyResponseWriterDelegator{delegate}
|
||||
}
|
||||
return delegate
|
||||
}
|
||||
16
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
generated
vendored
16
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
generated
vendored
|
|
@ -28,9 +28,10 @@ import (
|
|||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||
)
|
||||
|
||||
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
||||
|
|
@ -71,6 +72,11 @@ type APIGroupVersion struct {
|
|||
Linker runtime.SelfLinker
|
||||
UnsafeConvertor runtime.ObjectConvertor
|
||||
|
||||
// Authorizer determines whether a user is allowed to make a certain request. The Handler does a preliminary
|
||||
// authorization check using the request URI but it may be necessary to make additional checks, such as in
|
||||
// the create-on-update case
|
||||
Authorizer authorizer.Authorizer
|
||||
|
||||
Admit admission.Interface
|
||||
|
||||
MinRequestTimeout time.Duration
|
||||
|
|
@ -79,8 +85,12 @@ type APIGroupVersion struct {
|
|||
// if the client requests it via Accept-Encoding
|
||||
EnableAPIResponseCompression bool
|
||||
|
||||
// OpenAPIConfig lets the individual handlers build a subset of the OpenAPI schema before they are installed.
|
||||
OpenAPIConfig *openapicommon.Config
|
||||
// OpenAPIModels exposes the OpenAPI models to each individual handler.
|
||||
OpenAPIModels openapiproto.Models
|
||||
|
||||
// The limit on the request body size that would be accepted and decoded in a write request.
|
||||
// 0 means no limit.
|
||||
MaxRequestBodyBytes int64
|
||||
}
|
||||
|
||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||
|
|
|
|||
115
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
115
vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
generated
vendored
|
|
@ -17,22 +17,30 @@ limitations under the License.
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
|
||||
|
|
@ -41,8 +49,8 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
trace := utiltrace.New("Create " + req.URL.Path)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) {
|
||||
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +73,11 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
|
||||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
gv := scope.Kind.GroupVersion()
|
||||
s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
|
||||
|
|
@ -72,14 +85,28 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal})
|
||||
|
||||
body, err := readBody(req)
|
||||
decoder := scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion)
|
||||
|
||||
body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
options := &metav1.CreateOptions{}
|
||||
values := req.URL.Query()
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
if errs := validation.ValidateCreateOptions(options); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "CreateOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
defaultGVK := scope.Kind
|
||||
original := r.New()
|
||||
trace.Step("About to convert to expected version")
|
||||
|
|
@ -101,17 +128,28 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)
|
||||
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
|
||||
err = mutatingAdmission.Admit(admissionAttributes)
|
||||
err = mutatingAdmission.Admit(admissionAttributes, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace with content type negotiation?
|
||||
includeUninitialized := req.URL.Query().Get("includeUninitialized") == "1"
|
||||
if scope.FieldManager != nil {
|
||||
liveObj, err := scope.Creater.New(scope.Kind)
|
||||
if err != nil {
|
||||
scope.err(fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
obj, err = scope.FieldManager.Update(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
|
||||
if err != nil {
|
||||
scope.err(fmt.Errorf("failed to update object (Create for %v) managed fields: %v", scope.Kind, err), w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
trace.Step("About to store object in database")
|
||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||
|
|
@ -119,8 +157,8 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
ctx,
|
||||
name,
|
||||
obj,
|
||||
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes),
|
||||
includeUninitialized,
|
||||
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, &scope),
|
||||
options,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -129,30 +167,14 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||
}
|
||||
trace.Step("Object stored in database")
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
trace.Step("Self-link added")
|
||||
|
||||
// If the object is partially initialized, always indicate it via StatusAccepted
|
||||
code := http.StatusCreated
|
||||
if accessor, err := meta.Accessor(result); err == nil {
|
||||
if accessor.GetInitializers() != nil {
|
||||
code = http.StatusAccepted
|
||||
}
|
||||
}
|
||||
status, ok := result.(*metav1.Status)
|
||||
if ok && err == nil && status.Code == 0 {
|
||||
status.Code = int32(code)
|
||||
}
|
||||
|
||||
transformResponseObject(ctx, scope, req, w, code, result)
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, code, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -170,6 +192,35 @@ type namedCreaterAdapter struct {
|
|||
rest.Creater
|
||||
}
|
||||
|
||||
func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
|
||||
return c.Creater.Create(ctx, obj, createValidatingAdmission, includeUninitialized)
|
||||
func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
|
||||
}
|
||||
|
||||
// manager is assumed to be already a valid value, we need to make
|
||||
// userAgent into a valid value too.
|
||||
func managerOrUserAgent(manager, userAgent string) string {
|
||||
if manager != "" {
|
||||
return manager
|
||||
}
|
||||
return prefixFromUserAgent(userAgent)
|
||||
}
|
||||
|
||||
// prefixFromUserAgent takes the characters preceding the first /, quote
|
||||
// unprintable character and then trim what's beyond the
|
||||
// FieldManagerMaxLength limit.
|
||||
func prefixFromUserAgent(u string) string {
|
||||
m := strings.Split(u, "/")[0]
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, r := range m {
|
||||
// Ignore non-printable characters
|
||||
if !unicode.IsPrint(r) {
|
||||
continue
|
||||
}
|
||||
// Only append if we have room for it
|
||||
if buf.Len()+utf8.RuneLen(r) > validation.FieldManagerMaxLength {
|
||||
break
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
|||
133
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
133
vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
generated
vendored
|
|
@ -24,13 +24,18 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// DeleteResource returns a function that will handle a resource deletion
|
||||
|
|
@ -41,8 +46,8 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||
trace := utiltrace.New("Delete " + req.URL.Path)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) {
|
||||
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -59,9 +64,15 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
options := &metav1.DeleteOptions{}
|
||||
if allowsOptions {
|
||||
body, err := readBody(req)
|
||||
body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -90,28 +101,31 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
||||
trace.Step("Recorded the audit event")
|
||||
} else {
|
||||
if values := req.URL.Query(); len(values) > 0 {
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if errs := validation.ValidateDeleteOptions(options); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "DeleteOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace.Step("About to check admission control")
|
||||
if admit != nil && admit.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)
|
||||
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
if err := mutatingAdmission.Admit(attrs); err != nil {
|
||||
if err := mutatingAdmission.Admit(attrs, &scope); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
if err := validatingAdmission.Validate(attrs); err != nil {
|
||||
if err := validatingAdmission.Validate(attrs, &scope); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
|
@ -152,30 +166,21 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||
Kind: scope.Kind.Kind,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// when a non-status response is returned, set the self link
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if _, ok := result.(*metav1.Status); !ok {
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transformResponseObject(ctx, scope, req, w, status, result)
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCollection returns a function that will handle a collection deletion
|
||||
func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
if isDryRun(req.URL) {
|
||||
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||
trace := utiltrace.New("Delete " + req.URL.Path)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -191,26 +196,11 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
|
||||
if admit != nil && admit.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
err = mutatingAdmission.Admit(attrs)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
err = validatingAdmission.Validate(attrs)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
listOptions := metainternalversion.ListOptions{}
|
||||
|
|
@ -224,7 +214,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
// TODO: DecodeParametersInto should do this.
|
||||
if listOptions.FieldSelector != nil {
|
||||
fn := func(label, value string) (newLabel, newValue string, err error) {
|
||||
return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value)
|
||||
return scope.Convertor.ConvertFieldLabel(scope.Kind, label, value)
|
||||
}
|
||||
if listOptions.FieldSelector, err = listOptions.FieldSelector.Transform(fn); err != nil {
|
||||
// TODO: allow bad request to set field causes based on query parameters
|
||||
|
|
@ -236,7 +226,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
|
||||
options := &metav1.DeleteOptions{}
|
||||
if checkBody {
|
||||
body, err := readBody(req)
|
||||
body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -260,6 +250,38 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
|
||||
} else {
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if errs := validation.ValidateDeleteOptions(options); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "DeleteOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
if admit != nil && admit.Handles(admission.Delete) {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
err = mutatingAdmission.Admit(attrs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
err = validatingAdmission.Validate(attrs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -281,16 +303,9 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||
Kind: scope.Kind.Kind,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// when a non-status response is returned, set the self link
|
||||
if _, ok := result.(*metav1.Status); !ok {
|
||||
if _, err := setListSelfLink(result, ctx, req, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/OWNERS
generated
vendored
Normal file
5
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/OWNERS
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
approvers:
|
||||
- jennybuckley
|
||||
- apelisse
|
||||
reviewers:
|
||||
- kwiesmueller
|
||||
270
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
Normal file
270
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go
generated
vendored
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fieldmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/merge"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// FieldManager updates the managed fields and merge applied
|
||||
// configurations.
|
||||
type FieldManager struct {
|
||||
typeConverter internal.TypeConverter
|
||||
objectConverter runtime.ObjectConvertor
|
||||
objectDefaulter runtime.ObjectDefaulter
|
||||
groupVersion schema.GroupVersion
|
||||
hubVersion schema.GroupVersion
|
||||
updater merge.Updater
|
||||
}
|
||||
|
||||
// NewFieldManager creates a new FieldManager that merges apply requests
|
||||
// and update managed fields for other types of requests.
|
||||
func NewFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (*FieldManager, error) {
|
||||
typeConverter, err := internal.NewTypeConverter(models)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FieldManager{
|
||||
typeConverter: typeConverter,
|
||||
objectConverter: objectConverter,
|
||||
objectDefaulter: objectDefaulter,
|
||||
groupVersion: gv,
|
||||
hubVersion: hub,
|
||||
updater: merge.Updater{
|
||||
Converter: internal.NewVersionConverter(typeConverter, objectConverter, hub),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewCRDFieldManager creates a new FieldManager specifically for
|
||||
// CRDs. This doesn't use openapi models (and it doesn't support the
|
||||
// validation field right now).
|
||||
func NewCRDFieldManager(objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) *FieldManager {
|
||||
return &FieldManager{
|
||||
typeConverter: internal.DeducedTypeConverter{},
|
||||
objectConverter: objectConverter,
|
||||
objectDefaulter: objectDefaulter,
|
||||
groupVersion: gv,
|
||||
hubVersion: hub,
|
||||
updater: merge.Updater{
|
||||
Converter: internal.NewCRDVersionConverter(internal.DeducedTypeConverter{}, objectConverter, hub),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Update is used when the object has already been merged (non-apply
|
||||
// use-case), and simply updates the managed fields in the output
|
||||
// object.
|
||||
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) {
|
||||
// If the object doesn't have metadata, we should just return without trying to
|
||||
// set the managedFields at all, so creates/updates/patches will work normally.
|
||||
if _, err := meta.Accessor(newObj); err != nil {
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// First try to decode the managed fields provided in the update,
|
||||
// This is necessary to allow directly updating managed fields.
|
||||
managed, err := internal.DecodeObjectManagedFields(newObj)
|
||||
|
||||
// If the managed field is empty or we failed to decode it,
|
||||
// let's try the live object. This is to prevent clients who
|
||||
// don't understand managedFields from deleting it accidentally.
|
||||
if err != nil || len(managed) == 0 {
|
||||
managed, err = internal.DecodeObjectManagedFields(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||
}
|
||||
}
|
||||
newObjVersioned, err := f.toVersioned(newObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
||||
}
|
||||
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||
}
|
||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
||||
internal.RemoveObjectManagedFields(newObjVersioned)
|
||||
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed new object: %v", err)
|
||||
}
|
||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
||||
}
|
||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||
manager, err = f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||
}
|
||||
|
||||
managed, err = f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed, manager)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update ManagedFields: %v", err)
|
||||
}
|
||||
managed = f.stripFields(managed, manager)
|
||||
|
||||
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
|
||||
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// Apply is used when server-side apply is called, as it merges the
|
||||
// object and update the managed fields.
|
||||
func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager string, force bool) (runtime.Object, error) {
|
||||
// If the object doesn't have metadata, apply isn't allowed.
|
||||
if _, err := meta.Accessor(liveObj); err != nil {
|
||||
return nil, fmt.Errorf("couldn't get accessor: %v", err)
|
||||
}
|
||||
|
||||
managed, err := internal.DecodeObjectManagedFields(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
||||
}
|
||||
// Check that the patch object has the same version as the live object
|
||||
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
|
||||
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
|
||||
return nil, fmt.Errorf("error decoding YAML: %v", err)
|
||||
}
|
||||
if patchObj.GetAPIVersion() != f.groupVersion.String() {
|
||||
return nil,
|
||||
errors.NewBadRequest(
|
||||
fmt.Sprintf("Incorrect version specified in apply patch. "+
|
||||
"Specified patch version: %s, expected: %s",
|
||||
patchObj.GetAPIVersion(), f.groupVersion.String()))
|
||||
}
|
||||
|
||||
liveObjVersioned, err := f.toVersioned(liveObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
|
||||
}
|
||||
internal.RemoveObjectManagedFields(liveObjVersioned)
|
||||
|
||||
patchObjTyped, err := f.typeConverter.YAMLToTyped(patch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed patch object: %v", err)
|
||||
}
|
||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
||||
}
|
||||
manager, err := f.buildManagerInfo(fieldManager, metav1.ManagedFieldsOperationApply)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||
}
|
||||
|
||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||
newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, manager, force)
|
||||
if err != nil {
|
||||
if conflicts, ok := err.(merge.Conflicts); ok {
|
||||
return nil, internal.NewConflictError(conflicts)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
managed = f.stripFields(managed, manager)
|
||||
|
||||
newObj, err := f.typeConverter.TypedToObject(newObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert new typed object to object: %v", err)
|
||||
}
|
||||
|
||||
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
|
||||
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
|
||||
}
|
||||
|
||||
newObjVersioned, err := f.toVersioned(newObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
|
||||
}
|
||||
f.objectDefaulter.Default(newObjVersioned)
|
||||
|
||||
newObjUnversioned, err := f.toUnversioned(newObjVersioned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to unversioned: %v", err)
|
||||
}
|
||||
return newObjUnversioned, nil
|
||||
}
|
||||
|
||||
func (f *FieldManager) toVersioned(obj runtime.Object) (runtime.Object, error) {
|
||||
return f.objectConverter.ConvertToVersion(obj, f.groupVersion)
|
||||
}
|
||||
|
||||
func (f *FieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
|
||||
return f.objectConverter.ConvertToVersion(obj, f.hubVersion)
|
||||
}
|
||||
|
||||
func (f *FieldManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) {
|
||||
managerInfo := metav1.ManagedFieldsEntry{
|
||||
Manager: prefix,
|
||||
Operation: operation,
|
||||
APIVersion: f.groupVersion.String(),
|
||||
Time: &metav1.Time{Time: time.Now().UTC()},
|
||||
}
|
||||
if managerInfo.Manager == "" {
|
||||
managerInfo.Manager = "unknown"
|
||||
}
|
||||
return internal.BuildManagerIdentifier(&managerInfo)
|
||||
}
|
||||
|
||||
// stripSet is the list of fields that should never be part of a mangedFields.
|
||||
var stripSet = fieldpath.NewSet(
|
||||
fieldpath.MakePathOrDie("apiVersion"),
|
||||
fieldpath.MakePathOrDie("kind"),
|
||||
fieldpath.MakePathOrDie("metadata", "name"),
|
||||
fieldpath.MakePathOrDie("metadata", "namespace"),
|
||||
fieldpath.MakePathOrDie("metadata", "creationTimestamp"),
|
||||
fieldpath.MakePathOrDie("metadata", "selfLink"),
|
||||
fieldpath.MakePathOrDie("metadata", "uid"),
|
||||
fieldpath.MakePathOrDie("metadata", "clusterName"),
|
||||
fieldpath.MakePathOrDie("metadata", "generation"),
|
||||
fieldpath.MakePathOrDie("metadata", "managedFields"),
|
||||
fieldpath.MakePathOrDie("metadata", "resourceVersion"),
|
||||
)
|
||||
|
||||
// stripFields removes a predefined set of paths found in typed from managed and returns the updated ManagedFields
|
||||
func (f *FieldManager) stripFields(managed fieldpath.ManagedFields, manager string) fieldpath.ManagedFields {
|
||||
vs, ok := managed[manager]
|
||||
if ok {
|
||||
if vs == nil {
|
||||
panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager))
|
||||
}
|
||||
vs.Set = vs.Set.Difference(stripSet)
|
||||
if vs.Set.Empty() {
|
||||
delete(managed, manager)
|
||||
}
|
||||
}
|
||||
|
||||
return managed
|
||||
}
|
||||
82
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
Normal file
82
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/conflict.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/merge"
|
||||
)
|
||||
|
||||
// NewConflictError returns an error including details on the requests apply conflicts
|
||||
func NewConflictError(conflicts merge.Conflicts) *errors.StatusError {
|
||||
causes := []metav1.StatusCause{}
|
||||
for _, conflict := range conflicts {
|
||||
causes = append(causes, metav1.StatusCause{
|
||||
Type: metav1.CauseTypeFieldManagerConflict,
|
||||
Message: fmt.Sprintf("conflict with %v", printManager(conflict.Manager)),
|
||||
Field: conflict.Path.String(),
|
||||
})
|
||||
}
|
||||
return errors.NewApplyConflict(causes, getConflictMessage(conflicts))
|
||||
}
|
||||
|
||||
func getConflictMessage(conflicts merge.Conflicts) string {
|
||||
if len(conflicts) == 1 {
|
||||
return fmt.Sprintf("Apply failed with 1 conflict: conflict with %v: %v", printManager(conflicts[0].Manager), conflicts[0].Path)
|
||||
}
|
||||
|
||||
m := map[string][]fieldpath.Path{}
|
||||
for _, conflict := range conflicts {
|
||||
m[conflict.Manager] = append(m[conflict.Manager], conflict.Path)
|
||||
}
|
||||
|
||||
uniqueManagers := []string{}
|
||||
for manager := range m {
|
||||
uniqueManagers = append(uniqueManagers, manager)
|
||||
}
|
||||
|
||||
// Print conflicts by sorted managers.
|
||||
sort.Strings(uniqueManagers)
|
||||
|
||||
messages := []string{}
|
||||
for _, manager := range uniqueManagers {
|
||||
messages = append(messages, fmt.Sprintf("conflicts with %v:", printManager(manager)))
|
||||
for _, path := range m[manager] {
|
||||
messages = append(messages, fmt.Sprintf("- %v", path))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), strings.Join(messages, "\n"))
|
||||
}
|
||||
|
||||
func printManager(manager string) string {
|
||||
encodedManager := &metav1.ManagedFieldsEntry{}
|
||||
if err := json.Unmarshal([]byte(manager), encodedManager); err != nil {
|
||||
return fmt.Sprintf("%q", manager)
|
||||
}
|
||||
if encodedManager.Operation == metav1.ManagedFieldsOperationUpdate {
|
||||
return fmt.Sprintf("%q using %v at %v", encodedManager.Manager, encodedManager.APIVersion, encodedManager.Time.UTC().Format(time.RFC3339))
|
||||
}
|
||||
return fmt.Sprintf("%q", encodedManager.Manager)
|
||||
}
|
||||
95
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
Normal file
95
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fields.go
generated
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
)
|
||||
|
||||
func newFields() metav1.Fields {
|
||||
return metav1.Fields{Map: map[string]metav1.Fields{}}
|
||||
}
|
||||
|
||||
func fieldsSet(f metav1.Fields, path fieldpath.Path, set *fieldpath.Set) error {
|
||||
if len(f.Map) == 0 {
|
||||
set.Insert(path)
|
||||
}
|
||||
for k := range f.Map {
|
||||
if k == "." {
|
||||
set.Insert(path)
|
||||
continue
|
||||
}
|
||||
pe, err := NewPathElement(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = append(path, pe)
|
||||
err = fieldsSet(f.Map[k], path, set)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FieldsToSet creates a set paths from an input trie of fields
|
||||
func FieldsToSet(f metav1.Fields) (fieldpath.Set, error) {
|
||||
set := fieldpath.Set{}
|
||||
return set, fieldsSet(f, fieldpath.Path{}, &set)
|
||||
}
|
||||
|
||||
func removeUselessDots(f metav1.Fields) metav1.Fields {
|
||||
if _, ok := f.Map["."]; ok && len(f.Map) == 1 {
|
||||
delete(f.Map, ".")
|
||||
return f
|
||||
}
|
||||
for k, tf := range f.Map {
|
||||
f.Map[k] = removeUselessDots(tf)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// SetToFields creates a trie of fields from an input set of paths
|
||||
func SetToFields(s fieldpath.Set) (metav1.Fields, error) {
|
||||
var err error
|
||||
f := newFields()
|
||||
s.Iterate(func(path fieldpath.Path) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tf := f
|
||||
for _, pe := range path {
|
||||
var str string
|
||||
str, err = PathElementString(pe)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if _, ok := tf.Map[str]; ok {
|
||||
tf = tf.Map[str]
|
||||
} else {
|
||||
tf.Map[str] = newFields()
|
||||
tf = tf.Map[str]
|
||||
}
|
||||
}
|
||||
tf.Map["."] = newFields()
|
||||
})
|
||||
f = removeUselessDots(f)
|
||||
return f, err
|
||||
}
|
||||
120
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go
generated
vendored
Normal file
120
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/gvkparser.go
generated
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kube-openapi/pkg/schemaconv"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/typed"
|
||||
)
|
||||
|
||||
// groupVersionKindExtensionKey is the key used to lookup the
|
||||
// GroupVersionKind value for an object definition from the
|
||||
// definition's "extensions" map.
|
||||
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
||||
|
||||
type gvkParser struct {
|
||||
gvks map[schema.GroupVersionKind]string
|
||||
parser typed.Parser
|
||||
}
|
||||
|
||||
func (p *gvkParser) Type(gvk schema.GroupVersionKind) typed.ParseableType {
|
||||
typeName, ok := p.gvks[gvk]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p.parser.Type(typeName)
|
||||
}
|
||||
|
||||
func newGVKParser(models proto.Models) (*gvkParser, error) {
|
||||
typeSchema, err := schemaconv.ToSchema(models)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert models to schema: %v", err)
|
||||
}
|
||||
parser := gvkParser{
|
||||
gvks: map[schema.GroupVersionKind]string{},
|
||||
}
|
||||
parser.parser = typed.Parser{Schema: *typeSchema}
|
||||
for _, modelName := range models.ListModels() {
|
||||
model := models.LookupModel(modelName)
|
||||
if model == nil {
|
||||
panic(fmt.Sprintf("ListModels returns a model that can't be looked-up for: %v", modelName))
|
||||
}
|
||||
gvkList := parseGroupVersionKind(model)
|
||||
for _, gvk := range gvkList {
|
||||
if len(gvk.Kind) > 0 {
|
||||
_, ok := parser.gvks[gvk]
|
||||
if ok {
|
||||
return nil, fmt.Errorf("duplicate entry for %v", gvk)
|
||||
}
|
||||
parser.gvks[gvk] = modelName
|
||||
}
|
||||
}
|
||||
}
|
||||
return &parser, nil
|
||||
}
|
||||
|
||||
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
|
||||
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
|
||||
extensions := s.GetExtensions()
|
||||
|
||||
gvkListResult := []schema.GroupVersionKind{}
|
||||
|
||||
// Get the extensions
|
||||
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
// gvk extension must be a list of at least 1 element.
|
||||
gvkList, ok := gvkExtension.([]interface{})
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
for _, gvk := range gvkList {
|
||||
// gvk extension list must be a map with group, version, and
|
||||
// kind fields
|
||||
gvkMap, ok := gvk.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
group, ok := gvkMap["group"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version, ok := gvkMap["version"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
kind, ok := gvkMap["kind"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
|
||||
Group: group,
|
||||
Version: version,
|
||||
Kind: kind,
|
||||
})
|
||||
}
|
||||
|
||||
return gvkListResult
|
||||
}
|
||||
202
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
Normal file
202
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/managedfields.go
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
)
|
||||
|
||||
// RemoveObjectManagedFields removes the ManagedFields from the object
|
||||
// before we merge so that it doesn't appear in the ManagedFields
|
||||
// recursively.
|
||||
func RemoveObjectManagedFields(obj runtime.Object) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
accessor.SetManagedFields(nil)
|
||||
}
|
||||
|
||||
// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields.
|
||||
func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, error) {
|
||||
if from == nil {
|
||||
return make(map[string]*fieldpath.VersionedSet), nil
|
||||
}
|
||||
accessor, err := meta.Accessor(from)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
|
||||
managed, err := decodeManagedFields(accessor.GetManagedFields())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert managed fields from API: %v", err)
|
||||
}
|
||||
return managed, err
|
||||
}
|
||||
|
||||
// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields
|
||||
func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedFields) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("couldn't get accessor: %v", err))
|
||||
}
|
||||
|
||||
managed, err := encodeManagedFields(fields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert back managed fields to API: %v", err)
|
||||
}
|
||||
accessor.SetManagedFields(managed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeManagedFields converts ManagedFields from the wire format (api format)
|
||||
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) {
|
||||
managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields))
|
||||
for _, encodedVersionedSet := range encodedManagedFields {
|
||||
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
||||
}
|
||||
managedFields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
||||
}
|
||||
}
|
||||
return managedFields, nil
|
||||
}
|
||||
|
||||
// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry
|
||||
func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) {
|
||||
encodedManagerCopy := *encodedManager
|
||||
|
||||
// Never include the fields in the manager identifier
|
||||
encodedManagerCopy.Fields = nil
|
||||
|
||||
// For appliers, don't include the APIVersion or Time in the manager identifier,
|
||||
// so it will always have the same manager identifier each time it applied.
|
||||
if encodedManager.Operation == metav1.ManagedFieldsOperationApply {
|
||||
encodedManagerCopy.APIVersion = ""
|
||||
encodedManagerCopy.Time = nil
|
||||
}
|
||||
|
||||
// Use the remaining fields to build the manager identifier
|
||||
b, err := json.Marshal(&encodedManagerCopy)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error marshalling manager identifier: %v", err)
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet *fieldpath.VersionedSet, err error) {
|
||||
versionedSet = &fieldpath.VersionedSet{}
|
||||
versionedSet.APIVersion = fieldpath.APIVersion(encodedVersionedSet.APIVersion)
|
||||
if encodedVersionedSet.Operation == metav1.ManagedFieldsOperationApply {
|
||||
versionedSet.Applied = true
|
||||
}
|
||||
|
||||
fields := metav1.Fields{}
|
||||
if encodedVersionedSet.Fields != nil {
|
||||
fields = *encodedVersionedSet.Fields
|
||||
}
|
||||
set, err := FieldsToSet(fields)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding set: %v", err)
|
||||
}
|
||||
versionedSet.Set = &set
|
||||
return versionedSet, nil
|
||||
}
|
||||
|
||||
// encodeManagedFields converts ManagedFields from the the format used by
|
||||
// sigs.k8s.io/structured-merge-diff to the the wire format (api format)
|
||||
func encodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||
// Sort the keys so a predictable order will be used.
|
||||
managers := []string{}
|
||||
for manager := range managedFields {
|
||||
managers = append(managers, manager)
|
||||
}
|
||||
sort.Strings(managers)
|
||||
|
||||
encodedManagedFields = []metav1.ManagedFieldsEntry{}
|
||||
for _, manager := range managers {
|
||||
versionedSet := managedFields[manager]
|
||||
v, err := encodeManagerVersionedSet(manager, versionedSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
|
||||
}
|
||||
encodedManagedFields = append(encodedManagedFields, *v)
|
||||
}
|
||||
return sortEncodedManagedFields(encodedManagedFields)
|
||||
}
|
||||
|
||||
func sortEncodedManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (sortedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||
sort.Slice(encodedManagedFields, func(i, j int) bool {
|
||||
p, q := encodedManagedFields[i], encodedManagedFields[j]
|
||||
|
||||
if p.Operation != q.Operation {
|
||||
return p.Operation < q.Operation
|
||||
}
|
||||
|
||||
ntime := &metav1.Time{Time: time.Time{}}
|
||||
if p.Time == nil {
|
||||
p.Time = ntime
|
||||
}
|
||||
if q.Time == nil {
|
||||
q.Time = ntime
|
||||
}
|
||||
if !p.Time.Equal(q.Time) {
|
||||
return p.Time.Before(q.Time)
|
||||
}
|
||||
|
||||
return p.Manager < q.Manager
|
||||
})
|
||||
|
||||
return encodedManagedFields, nil
|
||||
}
|
||||
|
||||
func encodeManagerVersionedSet(manager string, versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) {
|
||||
encodedVersionedSet = &metav1.ManagedFieldsEntry{}
|
||||
|
||||
// Get as many fields as we can from the manager identifier
|
||||
err = json.Unmarshal([]byte(manager), encodedVersionedSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", manager, err)
|
||||
}
|
||||
|
||||
// Get the APIVersion, Operation, and Fields from the VersionedSet
|
||||
encodedVersionedSet.APIVersion = string(versionedSet.APIVersion)
|
||||
if versionedSet.Applied {
|
||||
encodedVersionedSet.Operation = metav1.ManagedFieldsOperationApply
|
||||
}
|
||||
fields, err := SetToFields(*versionedSet.Set)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error encoding set: %v", err)
|
||||
}
|
||||
encodedVersionedSet.Fields = &fields
|
||||
|
||||
return encodedVersionedSet, nil
|
||||
}
|
||||
140
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
Normal file
140
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/pathelement.go
generated
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/value"
|
||||
)
|
||||
|
||||
const (
|
||||
// Field indicates that the content of this path element is a field's name
|
||||
Field = "f"
|
||||
|
||||
// Value indicates that the content of this path element is a field's value
|
||||
Value = "v"
|
||||
|
||||
// Index indicates that the content of this path element is an index in an array
|
||||
Index = "i"
|
||||
|
||||
// Key indicates that the content of this path element is a key value map
|
||||
Key = "k"
|
||||
|
||||
// Separator separates the type of a path element from the contents
|
||||
Separator = ":"
|
||||
)
|
||||
|
||||
// NewPathElement parses a serialized path element
|
||||
func NewPathElement(s string) (fieldpath.PathElement, error) {
|
||||
split := strings.SplitN(s, Separator, 2)
|
||||
if len(split) < 2 {
|
||||
return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s)
|
||||
}
|
||||
switch split[0] {
|
||||
case Field:
|
||||
return fieldpath.PathElement{
|
||||
FieldName: &split[1],
|
||||
}, nil
|
||||
case Value:
|
||||
val, err := value.FromJSON([]byte(split[1]))
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
return fieldpath.PathElement{
|
||||
Value: &val,
|
||||
}, nil
|
||||
case Index:
|
||||
i, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
return fieldpath.PathElement{
|
||||
Index: &i,
|
||||
}, nil
|
||||
case Key:
|
||||
kv := map[string]json.RawMessage{}
|
||||
err := json.Unmarshal([]byte(split[1]), &kv)
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
fields := []value.Field{}
|
||||
for k, v := range kv {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
val, err := value.FromJSON(b)
|
||||
if err != nil {
|
||||
return fieldpath.PathElement{}, err
|
||||
}
|
||||
|
||||
fields = append(fields, value.Field{
|
||||
Name: k,
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
return fieldpath.PathElement{
|
||||
Key: fields,
|
||||
}, nil
|
||||
default:
|
||||
// Ignore unknown key types
|
||||
return fieldpath.PathElement{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// PathElementString serializes a path element
|
||||
func PathElementString(pe fieldpath.PathElement) (string, error) {
|
||||
switch {
|
||||
case pe.FieldName != nil:
|
||||
return Field + Separator + *pe.FieldName, nil
|
||||
case len(pe.Key) > 0:
|
||||
kv := map[string]json.RawMessage{}
|
||||
for _, k := range pe.Key {
|
||||
b, err := k.Value.ToJSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := json.RawMessage{}
|
||||
err = json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
kv[k.Name] = m
|
||||
}
|
||||
b, err := json.Marshal(kv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Key + ":" + string(b), nil
|
||||
case pe.Value != nil:
|
||||
b, err := pe.Value.ToJSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Value + ":" + string(b), nil
|
||||
case pe.Index != nil:
|
||||
return Index + ":" + strconv.Itoa(*pe.Index), nil
|
||||
default:
|
||||
return "", errors.New("Invalid type of path element")
|
||||
}
|
||||
}
|
||||
127
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go
generated
vendored
Normal file
127
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
"sigs.k8s.io/structured-merge-diff/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/value"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// TypeConverter allows you to convert from runtime.Object to
|
||||
// typed.TypedValue and the other way around.
|
||||
type TypeConverter interface {
|
||||
ObjectToTyped(runtime.Object) (typed.TypedValue, error)
|
||||
YAMLToTyped([]byte) (typed.TypedValue, error)
|
||||
TypedToObject(typed.TypedValue) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// DeducedTypeConverter is a TypeConverter for CRDs that don't have a
|
||||
// schema. It does implement the same interface though (and create the
|
||||
// same types of objects), so that everything can still work the same.
|
||||
// CRDs are merged with all their fields being "atomic" (lists
|
||||
// included).
|
||||
//
|
||||
// Note that this is not going to be sufficient for converting to/from
|
||||
// CRDs that have a schema defined (we don't support that schema yet).
|
||||
// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
|
||||
type DeducedTypeConverter struct{}
|
||||
|
||||
var _ TypeConverter = DeducedTypeConverter{}
|
||||
|
||||
// ObjectToTyped converts an object into a TypedValue with a "deduced type".
|
||||
func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) {
|
||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return typed.DeducedParseableType{}.FromUnstructured(u)
|
||||
}
|
||||
|
||||
// YAMLToTyped parses a yaml object into a TypedValue with a "deduced type".
|
||||
func (DeducedTypeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) {
|
||||
return typed.DeducedParseableType{}.FromYAML(typed.YAMLObject(from))
|
||||
}
|
||||
|
||||
// TypedToObject transforms the typed value into a runtime.Object. That
|
||||
// is not specific to deduced type.
|
||||
func (DeducedTypeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) {
|
||||
return valueToObject(value.AsValue())
|
||||
}
|
||||
|
||||
type typeConverter struct {
|
||||
parser *gvkParser
|
||||
}
|
||||
|
||||
var _ TypeConverter = &typeConverter{}
|
||||
|
||||
// NewTypeConverter builds a TypeConverter from a proto.Models. This
|
||||
// will automatically find the proper version of the object, and the
|
||||
// corresponding schema information.
|
||||
func NewTypeConverter(models proto.Models) (TypeConverter, error) {
|
||||
parser, err := newGVKParser(models)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &typeConverter{parser: parser}, nil
|
||||
}
|
||||
|
||||
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) {
|
||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
t := c.parser.Type(gvk)
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("no corresponding type for %v", gvk)
|
||||
}
|
||||
return t.FromUnstructured(u)
|
||||
}
|
||||
|
||||
func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) {
|
||||
unstructured := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
|
||||
if err := yaml.Unmarshal(from, &unstructured.Object); err != nil {
|
||||
return nil, fmt.Errorf("error decoding YAML: %v", err)
|
||||
}
|
||||
|
||||
gvk := unstructured.GetObjectKind().GroupVersionKind()
|
||||
t := c.parser.Type(gvk)
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("no corresponding type for %v", gvk)
|
||||
}
|
||||
return t.FromYAML(typed.YAMLObject(string(from)))
|
||||
}
|
||||
|
||||
func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) {
|
||||
return valueToObject(value.AsValue())
|
||||
}
|
||||
|
||||
func valueToObject(value *value.Value) (runtime.Object, error) {
|
||||
vu := value.ToUnstructured(false)
|
||||
u, ok := vu.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert typed to unstructured: want map, got %T", vu)
|
||||
}
|
||||
return &unstructured.Unstructured{Object: u}, nil
|
||||
}
|
||||
101
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go
generated
vendored
Normal file
101
vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/merge"
|
||||
"sigs.k8s.io/structured-merge-diff/typed"
|
||||
)
|
||||
|
||||
// versionConverter is an implementation of
|
||||
// sigs.k8s.io/structured-merge-diff/merge.Converter
|
||||
type versionConverter struct {
|
||||
typeConverter TypeConverter
|
||||
objectConvertor runtime.ObjectConvertor
|
||||
hubGetter func(from schema.GroupVersion) schema.GroupVersion
|
||||
}
|
||||
|
||||
var _ merge.Converter = &versionConverter{}
|
||||
|
||||
// NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor.
|
||||
func NewVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
return &versionConverter{
|
||||
typeConverter: t,
|
||||
objectConvertor: o,
|
||||
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
|
||||
return schema.GroupVersion{
|
||||
Group: from.Group,
|
||||
Version: h.Version,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewCRDVersionConverter builds a VersionConverter for CRDs from a TypeConverter and an ObjectConvertor.
|
||||
func NewCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
|
||||
return &versionConverter{
|
||||
typeConverter: t,
|
||||
objectConvertor: o,
|
||||
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
|
||||
return h
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert implements sigs.k8s.io/structured-merge-diff/merge.Converter
|
||||
func (v *versionConverter) Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) {
|
||||
// Convert the smd typed value to a kubernetes object.
|
||||
objectToConvert, err := v.typeConverter.TypedToObject(object)
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// Parse the target groupVersion.
|
||||
groupVersion, err := schema.ParseGroupVersion(string(version))
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// If attempting to convert to the same version as we already have, just return it.
|
||||
fromVersion := objectToConvert.GetObjectKind().GroupVersionKind().GroupVersion()
|
||||
if fromVersion == groupVersion {
|
||||
return object, nil
|
||||
}
|
||||
|
||||
// Convert to internal
|
||||
internalObject, err := v.objectConvertor.ConvertToVersion(objectToConvert, v.hubGetter(fromVersion))
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// Convert the object into the target version
|
||||
convertedObject, err := v.objectConvertor.ConvertToVersion(internalObject, groupVersion)
|
||||
if err != nil {
|
||||
return object, err
|
||||
}
|
||||
|
||||
// Convert the object back to a smd typed value and return it.
|
||||
return v.typeConverter.ObjectToTyped(convertedObject)
|
||||
}
|
||||
|
||||
// IsMissingVersionError
|
||||
func (v *versionConverter) IsMissingVersionError(err error) bool {
|
||||
return runtime.IsNotRegisteredError(err)
|
||||
}
|
||||
54
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
54
vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
generated
vendored
|
|
@ -25,7 +25,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
|
|
@ -33,10 +33,11 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// getterFunc performs a get request with the given context and object name. The request
|
||||
|
|
@ -58,23 +59,22 @@ func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := getter(ctx, name, req, trace)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
trace.Step("About to write a response")
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result)
|
||||
trace.Step("Transformed response object")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +185,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
opts := metainternalversion.ListOptions{}
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
|
|
@ -196,7 +202,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||
// TODO: DecodeParametersInto should do this.
|
||||
if opts.FieldSelector != nil {
|
||||
fn := func(label, value string) (newLabel, newValue string, err error) {
|
||||
return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value)
|
||||
return scope.Convertor.ConvertFieldLabel(scope.Kind, label, value)
|
||||
}
|
||||
if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil {
|
||||
// TODO: allow bad request to set field causes based on query parameters
|
||||
|
|
@ -242,7 +248,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||
if timeout == 0 && minRequestTimeout > 0 {
|
||||
timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0))
|
||||
}
|
||||
glog.V(2).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).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout)
|
||||
|
||||
watcher, err := rw.Watch(ctx, &opts)
|
||||
if err != nil {
|
||||
|
|
@ -250,7 +256,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||
return
|
||||
}
|
||||
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
|
||||
serveWatch(watcher, scope, req, w, timeout)
|
||||
})
|
||||
return
|
||||
|
|
@ -265,21 +271,9 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||
return
|
||||
}
|
||||
trace.Step("Listing from storage done")
|
||||
numberOfItems, err := setListSelfLink(result, ctx, req, scope.Namer)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
trace.Step("Self-linking done")
|
||||
// Ensure empty lists return a non-nil items slice
|
||||
if numberOfItems == 0 && meta.IsListType(result) {
|
||||
if err := meta.SetList(result, []runtime.Object{}); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems))
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result)
|
||||
trace.Step(fmt.Sprintf("Writing http response done (%d items)", meta.LenList(result)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
generated
vendored
|
|
@ -29,6 +29,7 @@ type errNotAcceptable struct {
|
|||
accepted []string
|
||||
}
|
||||
|
||||
// NewNotAcceptableError returns an error of NotAcceptable which contains specified string
|
||||
func NewNotAcceptableError(accepted []string) error {
|
||||
return errNotAcceptable{accepted}
|
||||
}
|
||||
|
|
@ -51,6 +52,7 @@ type errUnsupportedMediaType struct {
|
|||
accepted []string
|
||||
}
|
||||
|
||||
// NewUnsupportedMediaTypeError returns an error of UnsupportedMediaType which contains specified string
|
||||
func NewUnsupportedMediaTypeError(accepted []string) error {
|
||||
return errUnsupportedMediaType{accepted}
|
||||
}
|
||||
|
|
|
|||
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
generated
vendored
|
|
@ -22,7 +22,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/ww/goautoneg"
|
||||
"github.com/munnerz/goautoneg"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
|
@ -133,6 +133,8 @@ type EndpointRestrictions interface {
|
|||
AllowsStreamSchema(schema string) bool
|
||||
}
|
||||
|
||||
// DefaultEndpointRestrictions is the default EndpointRestrictions which allows
|
||||
// content-type negotiation to verify server support for specific options
|
||||
var DefaultEndpointRestrictions = emptyEndpointRestrictions{}
|
||||
|
||||
type emptyEndpointRestrictions struct{}
|
||||
|
|
|
|||
338
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
338
vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
generated
vendored
|
|
@ -24,8 +24,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/evanphx/json-patch"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
|
@ -35,10 +38,20 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
// maximum number of operations a single json patch may contain.
|
||||
maxJSONPatchOperations = 10000
|
||||
)
|
||||
|
||||
// PatchResource returns a function that will handle a resource patch.
|
||||
|
|
@ -48,8 +61,8 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
trace := utiltrace.New("Patch " + req.URL.Path)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) {
|
||||
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -82,63 +95,125 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
patchJS, err := readBody(req)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
patchBytes, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
options := &metav1.PatchOptions{}
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
if errs := validation.ValidatePatchOptions(options, patchType); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "PatchOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
admit = admission.WithAudit(admit, ae)
|
||||
|
||||
audit.LogRequestPatch(ae, patchJS)
|
||||
audit.LogRequestPatch(ae, patchBytes)
|
||||
trace.Step("Recorded the audit event")
|
||||
|
||||
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
baseContentType := runtime.ContentTypeJSON
|
||||
if patchType == types.ApplyPatchType {
|
||||
baseContentType = runtime.ContentTypeYAML
|
||||
}
|
||||
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("no serializer defined for JSON"), w, req)
|
||||
scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req)
|
||||
return
|
||||
}
|
||||
gv := scope.Kind.GroupVersion()
|
||||
|
||||
codec := runtime.NewCodec(
|
||||
scope.Serializer.EncoderForVersion(s.Serializer, gv),
|
||||
scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}),
|
||||
scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion),
|
||||
)
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)
|
||||
admissionCheck := func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && admit.Handles(admission.Update) {
|
||||
return mutatingAdmission.Admit(admission.NewAttributesRecord(updatedObject, currentObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
||||
}
|
||||
return nil
|
||||
staticCreateAttributes := admission.NewAttributesRecord(
|
||||
nil,
|
||||
nil,
|
||||
scope.Kind,
|
||||
namespace,
|
||||
name,
|
||||
scope.Resource,
|
||||
scope.Subresource,
|
||||
admission.Create,
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
userInfo)
|
||||
staticUpdateAttributes := admission.NewAttributesRecord(
|
||||
nil,
|
||||
nil,
|
||||
scope.Kind,
|
||||
namespace,
|
||||
name,
|
||||
scope.Resource,
|
||||
scope.Subresource,
|
||||
admission.Update,
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
userInfo,
|
||||
)
|
||||
|
||||
mutatingAdmission, _ := admit.(admission.MutationInterface)
|
||||
createAuthorizerAttributes := authorizer.AttributesRecord{
|
||||
User: userInfo,
|
||||
ResourceRequest: true,
|
||||
Path: req.URL.Path,
|
||||
Verb: "create",
|
||||
APIGroup: scope.Resource.Group,
|
||||
APIVersion: scope.Resource.Version,
|
||||
Resource: scope.Resource.Resource,
|
||||
Subresource: scope.Subresource,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
p := patcher{
|
||||
namer: scope.Namer,
|
||||
creater: scope.Creater,
|
||||
defaulter: scope.Defaulter,
|
||||
typer: scope.Typer,
|
||||
unsafeConvertor: scope.UnsafeConvertor,
|
||||
kind: scope.Kind,
|
||||
resource: scope.Resource,
|
||||
subresource: scope.Subresource,
|
||||
dryRun: dryrun.IsDryRun(options.DryRun),
|
||||
|
||||
createValidation: rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
|
||||
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
|
||||
admissionCheck: admissionCheck,
|
||||
objectInterfaces: &scope,
|
||||
|
||||
hubGroupVersion: scope.HubGroupVersion,
|
||||
|
||||
createValidation: withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticCreateAttributes, &scope), scope.Authorizer, createAuthorizerAttributes),
|
||||
updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticUpdateAttributes, &scope),
|
||||
admissionCheck: mutatingAdmission,
|
||||
|
||||
codec: codec,
|
||||
|
||||
timeout: timeout,
|
||||
options: options,
|
||||
|
||||
restPatcher: r,
|
||||
name: name,
|
||||
patchType: patchType,
|
||||
patchJS: patchJS,
|
||||
patchBytes: patchBytes,
|
||||
userAgent: req.UserAgent(),
|
||||
|
||||
trace: trace,
|
||||
}
|
||||
|
||||
result, err := p.patchResource(ctx)
|
||||
result, wasCreated, err := p.patchResource(ctx, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -156,7 +231,12 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||
}
|
||||
trace.Step("Self-link added")
|
||||
|
||||
transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
|
||||
status := http.StatusOK
|
||||
if wasCreated {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,24 +252,33 @@ type patcher struct {
|
|||
namer ScopeNamer
|
||||
creater runtime.ObjectCreater
|
||||
defaulter runtime.ObjectDefaulter
|
||||
typer runtime.ObjectTyper
|
||||
unsafeConvertor runtime.ObjectConvertor
|
||||
resource schema.GroupVersionResource
|
||||
kind schema.GroupVersionKind
|
||||
subresource string
|
||||
dryRun bool
|
||||
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
|
||||
hubGroupVersion schema.GroupVersion
|
||||
|
||||
// Validation functions
|
||||
createValidation rest.ValidateObjectFunc
|
||||
updateValidation rest.ValidateObjectUpdateFunc
|
||||
admissionCheck mutateObjectUpdateFunc
|
||||
admissionCheck admission.MutationInterface
|
||||
|
||||
codec runtime.Codec
|
||||
|
||||
timeout time.Duration
|
||||
options *metav1.PatchOptions
|
||||
|
||||
// Operation information
|
||||
restPatcher rest.Patcher
|
||||
name string
|
||||
patchType types.PatchType
|
||||
patchJS []byte
|
||||
patchBytes []byte
|
||||
userAgent string
|
||||
|
||||
trace *utiltrace.Trace
|
||||
|
||||
|
|
@ -197,19 +286,18 @@ type patcher struct {
|
|||
namespace string
|
||||
updatedObjectInfo rest.UpdatedObjectInfo
|
||||
mechanism patchMechanism
|
||||
}
|
||||
|
||||
func (p *patcher) toUnversioned(versionedObj runtime.Object) (runtime.Object, error) {
|
||||
gvk := p.kind.GroupKind().WithVersion(runtime.APIVersionInternal)
|
||||
return p.unsafeConvertor.ConvertToVersion(versionedObj, gvk.GroupVersion())
|
||||
forceAllowCreate bool
|
||||
}
|
||||
|
||||
type patchMechanism interface {
|
||||
applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error)
|
||||
createNewObject() (runtime.Object, error)
|
||||
}
|
||||
|
||||
type jsonPatcher struct {
|
||||
*patcher
|
||||
|
||||
fieldManager *fieldmanager.FieldManager
|
||||
}
|
||||
|
||||
func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
||||
|
|
@ -222,7 +310,7 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r
|
|||
// Apply the patch.
|
||||
patchedObjJS, err := p.applyJSPatch(currentObjJS)
|
||||
if err != nil {
|
||||
return nil, interpretPatchError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Construct the resulting typed, unversioned object.
|
||||
|
|
@ -231,21 +319,39 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if p.fieldManager != nil {
|
||||
if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
|
||||
return nil, fmt.Errorf("failed to update object (json PATCH for %v) managed fields: %v", p.kind, err)
|
||||
}
|
||||
}
|
||||
return objToUpdate, nil
|
||||
}
|
||||
|
||||
// patchJS applies the patch. Input and output objects must both have
|
||||
func (p *jsonPatcher) createNewObject() (runtime.Object, error) {
|
||||
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
|
||||
}
|
||||
|
||||
// applyJSPatch applies the patch. Input and output objects must both have
|
||||
// the external version, since that is what the patch must have been constructed against.
|
||||
func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
|
||||
switch p.patchType {
|
||||
case types.JSONPatchType:
|
||||
patchObj, err := jsonpatch.DecodePatch(p.patchJS)
|
||||
patchObj, err := jsonpatch.DecodePatch(p.patchBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.NewBadRequest(err.Error())
|
||||
}
|
||||
return patchObj.Apply(versionedJS)
|
||||
if len(patchObj) > maxJSONPatchOperations {
|
||||
return nil, errors.NewRequestEntityTooLargeError(
|
||||
fmt.Sprintf("The allowed maximum operations in a JSON patch is %d, got %d",
|
||||
maxJSONPatchOperations, len(patchObj)))
|
||||
}
|
||||
patchedJS, err := patchObj.Apply(versionedJS)
|
||||
if err != nil {
|
||||
return nil, errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
|
||||
}
|
||||
return patchedJS, nil
|
||||
case types.MergePatchType:
|
||||
return jsonpatch.MergePatch(versionedJS, p.patchJS)
|
||||
return jsonpatch.MergePatch(versionedJS, p.patchBytes)
|
||||
default:
|
||||
// only here as a safety net - go-restful filters content-type
|
||||
return nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType)
|
||||
|
|
@ -257,6 +363,7 @@ type smpPatcher struct {
|
|||
|
||||
// Schema
|
||||
schemaReferenceObj runtime.Object
|
||||
fieldManager *fieldmanager.FieldManager
|
||||
}
|
||||
|
||||
func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
||||
|
|
@ -270,28 +377,63 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := strategicPatchObject(p.codec, p.defaulter, currentVersionedObject, p.patchJS, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
|
||||
if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert the object back to unversioned (aka internal version).
|
||||
unversionedObjToUpdate, err := p.toUnversioned(versionedObjToUpdate)
|
||||
// Convert the object back to the hub version
|
||||
newObj, err := p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unversionedObjToUpdate, nil
|
||||
if p.fieldManager != nil {
|
||||
if newObj, err = p.fieldManager.Update(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
|
||||
return nil, fmt.Errorf("failed to update object (smp PATCH for %v) managed fields: %v", p.kind, err)
|
||||
}
|
||||
}
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// strategicPatchObject applies a strategic merge patch of <patchJS> to
|
||||
func (p *smpPatcher) createNewObject() (runtime.Object, error) {
|
||||
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
|
||||
}
|
||||
|
||||
type applyPatcher struct {
|
||||
patch []byte
|
||||
options *metav1.PatchOptions
|
||||
creater runtime.ObjectCreater
|
||||
kind schema.GroupVersionKind
|
||||
fieldManager *fieldmanager.FieldManager
|
||||
}
|
||||
|
||||
func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) {
|
||||
force := false
|
||||
if p.options.Force != nil {
|
||||
force = *p.options.Force
|
||||
}
|
||||
if p.fieldManager == nil {
|
||||
panic("FieldManager must be installed to run apply")
|
||||
}
|
||||
return p.fieldManager.Apply(obj, p.patch, p.options.FieldManager, force)
|
||||
}
|
||||
|
||||
func (p *applyPatcher) createNewObject() (runtime.Object, error) {
|
||||
obj, err := p.creater.New(p.kind)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new object: %v", obj)
|
||||
}
|
||||
return p.applyPatchToCurrentObject(obj)
|
||||
}
|
||||
|
||||
// strategicPatchObject applies a strategic merge patch of <patchBytes> to
|
||||
// <originalObject> and stores the result in <objToUpdate>.
|
||||
// It additionally returns the map[string]interface{} representation of the
|
||||
// <originalObject> and <patchJS>.
|
||||
// <originalObject> and <patchBytes>.
|
||||
// NOTE: Both <originalObject> and <objToUpdate> are supposed to be versioned.
|
||||
func strategicPatchObject(
|
||||
codec runtime.Codec,
|
||||
defaulter runtime.ObjectDefaulter,
|
||||
originalObject runtime.Object,
|
||||
patchJS []byte,
|
||||
patchBytes []byte,
|
||||
objToUpdate runtime.Object,
|
||||
schemaReferenceObj runtime.Object,
|
||||
) error {
|
||||
|
|
@ -301,11 +443,11 @@ func strategicPatchObject(
|
|||
}
|
||||
|
||||
patchMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal(patchJS, &patchMap); err != nil {
|
||||
if err := json.Unmarshal(patchBytes, &patchMap); err != nil {
|
||||
return errors.NewBadRequest(err.Error())
|
||||
}
|
||||
|
||||
if err := applyPatchToObject(codec, defaulter, originalObjMap, patchMap, objToUpdate, schemaReferenceObj); err != nil {
|
||||
if err := applyPatchToObject(defaulter, originalObjMap, patchMap, objToUpdate, schemaReferenceObj); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
@ -313,59 +455,119 @@ func strategicPatchObject(
|
|||
|
||||
// applyPatch is called every time GuaranteedUpdate asks for the updated object,
|
||||
// and is given the currently persisted object as input.
|
||||
func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (runtime.Object, error) {
|
||||
// TODO: rename this function because the name implies it is related to applyPatcher
|
||||
func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
|
||||
// Make sure we actually have a persisted currentObject
|
||||
p.trace.Step("About to apply patch")
|
||||
if hasUID, err := hasUID(currentObject); err != nil {
|
||||
currentObjectHasUID, err := hasUID(currentObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasUID {
|
||||
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
|
||||
} else if !currentObjectHasUID {
|
||||
objToUpdate, patchErr = p.mechanism.createNewObject()
|
||||
} else {
|
||||
objToUpdate, patchErr = p.mechanism.applyPatchToCurrentObject(currentObject)
|
||||
}
|
||||
|
||||
objToUpdate, err := p.mechanism.applyPatchToCurrentObject(currentObject)
|
||||
if patchErr != nil {
|
||||
return nil, patchErr
|
||||
}
|
||||
|
||||
objToUpdateHasUID, err := hasUID(objToUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if objToUpdateHasUID && !currentObjectHasUID {
|
||||
accessor, err := meta.Accessor(objToUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.NewConflict(p.resource.GroupResource(), p.name, fmt.Errorf("uid mismatch: the provided object specified uid %s, and no existing object was found", accessor.GetUID()))
|
||||
}
|
||||
|
||||
if err := checkName(objToUpdate, p.name, p.namespace, p.namer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return objToUpdate, nil
|
||||
}
|
||||
|
||||
func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation) admission.Attributes {
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, p.dryRun, userInfo)
|
||||
}
|
||||
|
||||
// applyAdmission is called every time GuaranteedUpdate asks for the updated object,
|
||||
// and is given the currently persisted object and the patched object as input.
|
||||
// TODO: rename this function because the name implies it is related to applyPatcher
|
||||
func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) {
|
||||
p.trace.Step("About to check admission control")
|
||||
return patchedObject, p.admissionCheck(patchedObject, currentObject)
|
||||
var operation admission.Operation
|
||||
if hasUID, err := hasUID(currentObject); err != nil {
|
||||
return nil, err
|
||||
} else if !hasUID {
|
||||
operation = admission.Create
|
||||
currentObject = nil
|
||||
} else {
|
||||
operation = admission.Update
|
||||
}
|
||||
if p.admissionCheck != nil && p.admissionCheck.Handles(operation) {
|
||||
attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation)
|
||||
return patchedObject, p.admissionCheck.Admit(attributes, p.objectInterfaces)
|
||||
}
|
||||
return patchedObject, nil
|
||||
}
|
||||
|
||||
// patchResource divides PatchResource for easier unit testing
|
||||
func (p *patcher) patchResource(ctx context.Context) (runtime.Object, error) {
|
||||
func (p *patcher) patchResource(ctx context.Context, scope RequestScope) (runtime.Object, bool, error) {
|
||||
p.namespace = request.NamespaceValue(ctx)
|
||||
switch p.patchType {
|
||||
case types.JSONPatchType, types.MergePatchType:
|
||||
p.mechanism = &jsonPatcher{patcher: p}
|
||||
p.mechanism = &jsonPatcher{
|
||||
patcher: p,
|
||||
fieldManager: scope.FieldManager,
|
||||
}
|
||||
case types.StrategicMergePatchType:
|
||||
schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion())
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
p.mechanism = &smpPatcher{
|
||||
patcher: p,
|
||||
schemaReferenceObj: schemaReferenceObj,
|
||||
fieldManager: scope.FieldManager,
|
||||
}
|
||||
// this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type
|
||||
case types.ApplyPatchType:
|
||||
p.mechanism = &applyPatcher{
|
||||
fieldManager: scope.FieldManager,
|
||||
patch: p.patchBytes,
|
||||
options: p.options,
|
||||
creater: p.creater,
|
||||
kind: p.kind,
|
||||
}
|
||||
p.forceAllowCreate = true
|
||||
default:
|
||||
return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType)
|
||||
}
|
||||
|
||||
wasCreated := false
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
|
||||
result, err := finishRequest(p.timeout, func() (runtime.Object, error) {
|
||||
// TODO: Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate
|
||||
options, err := patchToUpdateOptions(p.options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.mechanism = &smpPatcher{patcher: p, schemaReferenceObj: schemaReferenceObj}
|
||||
default:
|
||||
return nil, fmt.Errorf("%v: unimplemented patch type", p.patchType)
|
||||
}
|
||||
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
|
||||
return finishRequest(p.timeout, func() (runtime.Object, error) {
|
||||
updateObject, _, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation)
|
||||
updateObject, created, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, p.forceAllowCreate, options)
|
||||
wasCreated = created
|
||||
return updateObject, updateErr
|
||||
})
|
||||
return result, wasCreated, err
|
||||
}
|
||||
|
||||
// applyPatchToObject applies a strategic merge patch of <patchMap> to
|
||||
// <originalMap> and stores the result in <objToUpdate>.
|
||||
// NOTE: <objToUpdate> must be a versioned object.
|
||||
func applyPatchToObject(
|
||||
codec runtime.Codec,
|
||||
defaulter runtime.ObjectDefaulter,
|
||||
originalMap map[string]interface{},
|
||||
patchMap map[string]interface{},
|
||||
|
|
@ -374,7 +576,7 @@ func applyPatchToObject(
|
|||
) error {
|
||||
patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, schemaReferenceObj)
|
||||
if err != nil {
|
||||
return interpretPatchError(err)
|
||||
return interpretStrategicMergePatchError(err)
|
||||
}
|
||||
|
||||
// Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object
|
||||
|
|
@ -387,8 +589,8 @@ func applyPatchToObject(
|
|||
return nil
|
||||
}
|
||||
|
||||
// interpretPatchError interprets the error type and returns an error with appropriate HTTP code.
|
||||
func interpretPatchError(err error) error {
|
||||
// interpretStrategicMergePatchError interprets the error type and returns an error with appropriate HTTP code.
|
||||
func interpretStrategicMergePatchError(err error) error {
|
||||
switch err {
|
||||
case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat:
|
||||
return errors.NewBadRequest(err.Error())
|
||||
|
|
@ -398,3 +600,13 @@ func interpretPatchError(err error) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func patchToUpdateOptions(po *metav1.PatchOptions) (*metav1.UpdateOptions, error) {
|
||||
b, err := json.Marshal(po)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uo := metav1.UpdateOptions{}
|
||||
err = json.Unmarshal(b, &uo)
|
||||
return &uo, err
|
||||
}
|
||||
|
|
|
|||
288
vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
generated
vendored
288
vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
generated
vendored
|
|
@ -27,149 +27,85 @@ import (
|
|||
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/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// transformResponseObject takes an object loaded from storage and performs any necessary transformations.
|
||||
// Will write the complete response object.
|
||||
func transformResponseObject(ctx context.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, result runtime.Object) {
|
||||
// TODO: fetch the media type much earlier in request processing and pass it into this method.
|
||||
mediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
status := responsewriters.ErrorToAPIStatus(err)
|
||||
responsewriters.WriteRawJSON(int(status.Code), status, w)
|
||||
func transformResponseObject(ctx context.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, mediaType negotiation.MediaTypeOptions, result runtime.Object) {
|
||||
// status objects are ignored for transformation
|
||||
if _, ok := result.(*metav1.Status); ok {
|
||||
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// If conversion was allowed by the scope, perform it before writing the response
|
||||
if target := mediaType.Convert; target != nil {
|
||||
switch {
|
||||
|
||||
case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
if meta.IsListType(result) {
|
||||
// TODO: this should be calculated earlier
|
||||
err = newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result))
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
m, err := meta.Accessor(result)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, partial)
|
||||
return
|
||||
|
||||
case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
if !meta.IsListType(result) {
|
||||
// TODO: this should be calculated earlier
|
||||
err = newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result))
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
list := &metav1beta1.PartialObjectMetadataList{}
|
||||
err := meta.EachListItem(result, func(obj runtime.Object) error {
|
||||
m, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
list.Items = append(list.Items, partial)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, list)
|
||||
return
|
||||
|
||||
case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
// TODO: relax the version abstraction
|
||||
// TODO: skip if this is a status response (delete without body)?
|
||||
|
||||
opts := &metav1beta1.TableOptions{}
|
||||
if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
for i := range table.Rows {
|
||||
item := &table.Rows[i]
|
||||
switch opts.IncludeObject {
|
||||
case metav1beta1.IncludeObject:
|
||||
item.Object.Object, err = scope.Convertor.ConvertToVersion(item.Object.Object, scope.Kind.GroupVersion())
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
// TODO: rely on defaulting for the value here?
|
||||
case metav1beta1.IncludeMetadata, "":
|
||||
m, err := meta.Accessor(item.Object.Object)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
// TODO: turn this into an internal type and do conversion in order to get object kind automatically set?
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
item.Object.Object = partial
|
||||
case metav1beta1.IncludeNone:
|
||||
item.Object.Object = nil
|
||||
default:
|
||||
// TODO: move this to validation on the table options?
|
||||
err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject))
|
||||
scope.err(err, w, req)
|
||||
}
|
||||
}
|
||||
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, table)
|
||||
return
|
||||
|
||||
default:
|
||||
// this block should only be hit if scope AllowsConversion is incorrect
|
||||
accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
|
||||
err := negotiation.NewNotAcceptableError(accepted)
|
||||
status := responsewriters.ErrorToAPIStatus(err)
|
||||
responsewriters.WriteRawJSON(int(status.Code), status, w)
|
||||
return
|
||||
}
|
||||
// ensure the self link and empty list array are set
|
||||
if err := setObjectSelfLink(ctx, result, req, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
trace := scope.Trace
|
||||
|
||||
// If conversion was allowed by the scope, perform it before writing the response
|
||||
switch target := mediaType.Convert; {
|
||||
|
||||
case target == nil:
|
||||
trace.Step("Writing response")
|
||||
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
|
||||
|
||||
case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
partial, err := asV1Beta1PartialObjectMetadata(result)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeMetaInternalVersion(partial, statusCode, w, req, &scope, target.GroupVersion()); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
trace.Step("Processing list items")
|
||||
partial, err := asV1Beta1PartialObjectMetadataList(result)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeMetaInternalVersion(partial, statusCode, w, req, &scope, target.GroupVersion()); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
|
||||
opts := &metav1beta1.TableOptions{}
|
||||
trace.Step("Decoding parameters")
|
||||
if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
table, err := asV1Beta1Table(ctx, result, opts, scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if err := writeMetaInternalVersion(table, statusCode, w, req, &scope, target.GroupVersion()); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
// this block should only be hit if scope AllowsConversion is incorrect
|
||||
accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
|
||||
err := negotiation.NewNotAcceptableError(accepted)
|
||||
scope.err(err, w, req)
|
||||
}
|
||||
}
|
||||
|
||||
// errNotAcceptable indicates Accept negotiation has failed
|
||||
|
|
@ -193,3 +129,91 @@ func (e errNotAcceptable) Status() metav1.Status {
|
|||
Message: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta1.TableOptions, scope RequestScope) (runtime.Object, error) {
|
||||
trace := scope.Trace
|
||||
|
||||
trace.Step("Converting to table")
|
||||
table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trace.Step("Processing rows")
|
||||
for i := range table.Rows {
|
||||
item := &table.Rows[i]
|
||||
switch opts.IncludeObject {
|
||||
case metav1beta1.IncludeObject:
|
||||
item.Object.Object, err = scope.Convertor.ConvertToVersion(item.Object.Object, scope.Kind.GroupVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: rely on defaulting for the value here?
|
||||
case metav1beta1.IncludeMetadata, "":
|
||||
m, err := meta.Accessor(item.Object.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: turn this into an internal type and do conversion in order to get object kind automatically set?
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
item.Object.Object = partial
|
||||
case metav1beta1.IncludeNone:
|
||||
item.Object.Object = nil
|
||||
default:
|
||||
// TODO: move this to validation on the table options?
|
||||
err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func asV1Beta1PartialObjectMetadata(result runtime.Object) (runtime.Object, error) {
|
||||
if meta.IsListType(result) {
|
||||
// TODO: this should be calculated earlier
|
||||
err := newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result))
|
||||
return nil, err
|
||||
}
|
||||
m, err := meta.Accessor(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
return partial, nil
|
||||
}
|
||||
|
||||
func asV1Beta1PartialObjectMetadataList(result runtime.Object) (runtime.Object, error) {
|
||||
if !meta.IsListType(result) {
|
||||
// TODO: this should be calculated earlier
|
||||
return nil, newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result))
|
||||
}
|
||||
list := &metav1beta1.PartialObjectMetadataList{}
|
||||
err := meta.EachListItem(result, func(obj runtime.Object) error {
|
||||
m, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
partial := meta.AsPartialObjectMetadata(m)
|
||||
partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
|
||||
list.Items = append(list.Items, partial)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func writeMetaInternalVersion(obj runtime.Object, statusCode int, w http.ResponseWriter, req *http.Request, restrictions negotiation.EndpointRestrictions, target schema.GroupVersion) error {
|
||||
// renegotiate under the internal version
|
||||
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, restrictions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, target)
|
||||
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, obj)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
27
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go
generated
vendored
27
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go
generated
vendored
|
|
@ -32,14 +32,6 @@ import (
|
|||
// Avoid emitting errors that look like valid HTML. Quotes are okay.
|
||||
var sanitizer = strings.NewReplacer(`&`, "&", `<`, "<", `>`, ">")
|
||||
|
||||
// BadGatewayError renders a simple bad gateway error.
|
||||
func BadGatewayError(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
fmt.Fprintf(w, "Bad Gateway: %q", sanitizer.Replace(req.RequestURI))
|
||||
}
|
||||
|
||||
// Forbidden renders a simple forbidden error
|
||||
func Forbidden(ctx context.Context, attributes authorizer.Attributes, w http.ResponseWriter, req *http.Request, reason string, s runtime.NegotiatedSerializer) {
|
||||
msg := sanitizer.Replace(forbiddenMessage(attributes))
|
||||
|
|
@ -67,31 +59,20 @@ func forbiddenMessage(attributes authorizer.Attributes) string {
|
|||
}
|
||||
|
||||
resource := attributes.GetResource()
|
||||
if group := attributes.GetAPIGroup(); len(group) > 0 {
|
||||
resource = resource + "." + group
|
||||
}
|
||||
if subresource := attributes.GetSubresource(); len(subresource) > 0 {
|
||||
resource = resource + "/" + subresource
|
||||
}
|
||||
|
||||
if ns := attributes.GetNamespace(); len(ns) > 0 {
|
||||
return fmt.Sprintf("User %q cannot %s %s in the namespace %q", username, attributes.GetVerb(), resource, ns)
|
||||
return fmt.Sprintf("User %q cannot %s resource %q in API group %q in the namespace %q", username, attributes.GetVerb(), resource, attributes.GetAPIGroup(), ns)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("User %q cannot %s %s at the cluster scope", username, attributes.GetVerb(), resource)
|
||||
return fmt.Sprintf("User %q cannot %s resource %q in API group %q at the cluster scope", username, attributes.GetVerb(), resource, attributes.GetAPIGroup())
|
||||
}
|
||||
|
||||
// InternalError renders a simple internal error
|
||||
func InternalError(w http.ResponseWriter, req *http.Request, err error) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Internal Server Error: %q: %v", sanitizer.Replace(req.RequestURI), err)
|
||||
http.Error(w, sanitizer.Replace(fmt.Sprintf("Internal Server Error: %q: %v", req.RequestURI, err)),
|
||||
http.StatusInternalServerError)
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
|
||||
// NotFound renders a simple not found error.
|
||||
func NotFound(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Not Found: %q", sanitizer.Replace(req.RequestURI))
|
||||
}
|
||||
|
|
|
|||
15
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
generated
vendored
15
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
generated
vendored
|
|
@ -38,11 +38,18 @@ func ErrorToAPIStatus(err error) *metav1.Status {
|
|||
if len(status.Status) == 0 {
|
||||
status.Status = metav1.StatusFailure
|
||||
}
|
||||
if status.Code == 0 {
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
if status.Code == 0 {
|
||||
status.Code = http.StatusOK
|
||||
case metav1.StatusFailure:
|
||||
}
|
||||
case metav1.StatusFailure:
|
||||
if status.Code == 0 {
|
||||
status.Code = http.StatusInternalServerError
|
||||
}
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("apiserver received an error with wrong status field : %#+v", err))
|
||||
if status.Code == 0 {
|
||||
status.Code = http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
54
vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
generated
vendored
|
|
@ -35,6 +35,26 @@ import (
|
|||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
)
|
||||
|
||||
// httpResponseWriterWithInit wraps http.ResponseWriter, and implements the io.Writer interface to be used
|
||||
// with encoding. The purpose is to allow for encoding to a stream, while accommodating a custom HTTP status code
|
||||
// if encoding fails, and meeting the encoder's io.Writer interface requirement.
|
||||
type httpResponseWriterWithInit struct {
|
||||
hasWritten bool
|
||||
mediaType string
|
||||
statusCode int
|
||||
innerW http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w httpResponseWriterWithInit) Write(b []byte) (n int, err error) {
|
||||
if !w.hasWritten {
|
||||
w.innerW.Header().Set("Content-Type", w.mediaType)
|
||||
w.innerW.WriteHeader(w.statusCode)
|
||||
w.hasWritten = true
|
||||
}
|
||||
|
||||
return w.innerW.Write(b)
|
||||
}
|
||||
|
||||
// WriteObject renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
||||
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
||||
// response. The Accept header and current API version will be passed in, and the output will be copied
|
||||
|
|
@ -44,7 +64,7 @@ func WriteObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSer
|
|||
stream, ok := object.(rest.ResourceStreamer)
|
||||
if ok {
|
||||
requestInfo, _ := request.RequestInfoFrom(req.Context())
|
||||
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
|
||||
StreamObject(statusCode, gv, s, stream, w, req)
|
||||
})
|
||||
return
|
||||
|
|
@ -56,7 +76,7 @@ func WriteObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSer
|
|||
// If the client requests a websocket upgrade, negotiate for a websocket reader protocol (because many
|
||||
// browser clients cannot easily handle binary streaming protocols).
|
||||
func StreamObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, stream rest.ResourceStreamer, w http.ResponseWriter, req *http.Request) {
|
||||
out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept"))
|
||||
out, flush, contentType, err := stream.InputStream(req.Context(), gv.String(), req.Header.Get("Accept"))
|
||||
if err != nil {
|
||||
ErrorNegotiated(err, s, gv, w, req)
|
||||
return
|
||||
|
|
@ -81,6 +101,10 @@ func StreamObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSe
|
|||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.WriteHeader(statusCode)
|
||||
// Flush headers, if possible
|
||||
if flusher, ok := w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
writer := w.(io.Writer)
|
||||
if flush {
|
||||
writer = flushwriter.Wrap(w)
|
||||
|
|
@ -90,12 +114,11 @@ func StreamObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSe
|
|||
|
||||
// SerializeObject renders an object in the content type negotiated by the client using the provided encoder.
|
||||
// The context is optional and can be nil.
|
||||
func SerializeObject(mediaType string, encoder runtime.Encoder, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
w.Header().Set("Content-Type", mediaType)
|
||||
w.WriteHeader(statusCode)
|
||||
func SerializeObject(mediaType string, encoder runtime.Encoder, innerW http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
w := httpResponseWriterWithInit{mediaType: mediaType, innerW: innerW, statusCode: statusCode}
|
||||
|
||||
if err := encoder.Encode(object, w); err != nil {
|
||||
errorJSONFatal(err, encoder, w)
|
||||
errSerializationFatal(err, encoder, w)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,22 +166,23 @@ func ErrorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupV
|
|||
return code
|
||||
}
|
||||
|
||||
// errorJSONFatal renders an error to the response, and if codec fails will render plaintext.
|
||||
// errSerializationFatal renders an error to the response, and if codec fails will render plaintext.
|
||||
// Returns the HTTP status code of the error.
|
||||
func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int {
|
||||
func errSerializationFatal(err error, codec runtime.Encoder, w httpResponseWriterWithInit) {
|
||||
utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
|
||||
status := ErrorToAPIStatus(err)
|
||||
code := int(status.Code)
|
||||
candidateStatusCode := int(status.Code)
|
||||
// If original statusCode was not successful, we need to return the original error.
|
||||
// We cannot hide it behind serialization problems
|
||||
if w.statusCode >= http.StatusOK && w.statusCode < http.StatusBadRequest {
|
||||
w.statusCode = candidateStatusCode
|
||||
}
|
||||
output, err := runtime.Encode(codec, status)
|
||||
if err != nil {
|
||||
w.WriteHeader(code)
|
||||
fmt.Fprintf(w, "%s: %s", status.Reason, status.Message)
|
||||
return code
|
||||
w.mediaType = "text/plain"
|
||||
output = []byte(fmt.Sprintf("%s: %s", status.Reason, status.Message))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
w.Write(output)
|
||||
return code
|
||||
}
|
||||
|
||||
// WriteRawJSON writes a non-API object in JSON.
|
||||
|
|
|
|||
106
vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
generated
vendored
106
vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
generated
vendored
|
|
@ -20,26 +20,29 @@ import (
|
|||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"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"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||
"k8s.io/klog"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// RequestScope encapsulates common fields across all RESTful handler methods.
|
||||
|
|
@ -54,15 +57,22 @@ type RequestScope struct {
|
|||
Defaulter runtime.ObjectDefaulter
|
||||
Typer runtime.ObjectTyper
|
||||
UnsafeConvertor runtime.ObjectConvertor
|
||||
Authorizer authorizer.Authorizer
|
||||
Trace *utiltrace.Trace
|
||||
|
||||
TableConvertor rest.TableConvertor
|
||||
OpenAPISchema openapiproto.Schema
|
||||
FieldManager *fieldmanager.FieldManager
|
||||
|
||||
Resource schema.GroupVersionResource
|
||||
Kind schema.GroupVersionKind
|
||||
Subresource string
|
||||
|
||||
MetaGroupVersion schema.GroupVersion
|
||||
|
||||
// HubGroupVersion indicates what version objects read from etcd or incoming requests should be converted to for in-memory handling.
|
||||
HubGroupVersion schema.GroupVersion
|
||||
|
||||
MaxRequestBodyBytes int64
|
||||
}
|
||||
|
||||
func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Request) {
|
||||
|
|
@ -93,9 +103,21 @@ func (scope *RequestScope) AllowsStreamSchema(s string) bool {
|
|||
return s == "watch"
|
||||
}
|
||||
|
||||
var _ admission.ObjectInterfaces = &RequestScope{}
|
||||
|
||||
func (r *RequestScope) GetObjectCreater() runtime.ObjectCreater { return r.Creater }
|
||||
func (r *RequestScope) GetObjectTyper() runtime.ObjectTyper { return r.Typer }
|
||||
func (r *RequestScope) GetObjectDefaulter() runtime.ObjectDefaulter { return r.Defaulter }
|
||||
func (r *RequestScope) GetObjectConvertor() runtime.ObjectConvertor { return r.Convertor }
|
||||
|
||||
// ConnectResource returns a function that handles a connect request on a rest.Storage object.
|
||||
func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
if isDryRun(req.URL) {
|
||||
scope.err(errors.NewBadRequest("dryRun is not supported"), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
namespace, name, err := scope.Namer.Name(req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
|
|
@ -113,22 +135,17 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi
|
|||
return
|
||||
}
|
||||
if admit != nil && admit.Handles(admission.Connect) {
|
||||
connectRequest := &rest.ConnectRequest{
|
||||
Name: name,
|
||||
Options: opts,
|
||||
ResourcePath: restPath,
|
||||
}
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
// TODO: remove the mutating admission here as soon as we have ported all plugin that handle CONNECT
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
err = mutatingAdmission.Admit(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo))
|
||||
err = mutatingAdmission.Admit(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo), &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
|
||||
err = validatingAdmission.Validate(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo))
|
||||
err = validatingAdmission.Validate(admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, false, userInfo), &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
|
|
@ -136,7 +153,7 @@ func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admissi
|
|||
}
|
||||
}
|
||||
requestInfo, _ := request.RequestInfoFrom(ctx)
|
||||
metrics.RecordLongRunning(req, requestInfo, func() {
|
||||
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
|
||||
handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, w: w})
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
|
|
@ -175,10 +192,17 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
|
|||
panicCh := make(chan interface{}, 1)
|
||||
go func() {
|
||||
// panics don't cross goroutine boundaries, so we have to handle ourselves
|
||||
defer utilruntime.HandleCrash(func(panicReason interface{}) {
|
||||
// Propagate to parent goroutine
|
||||
panicCh <- panicReason
|
||||
})
|
||||
defer func() {
|
||||
panicReason := recover()
|
||||
if panicReason != nil {
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:goruntime.Stack(buf, false)]
|
||||
panicReason = strings.TrimSuffix(fmt.Sprintf("%v\n%s", panicReason, string(buf)), "\n")
|
||||
// Propagate to parent goroutine
|
||||
panicCh <- panicReason
|
||||
}
|
||||
}()
|
||||
|
||||
if result, err := fn(); err != nil {
|
||||
errCh <- err
|
||||
|
|
@ -200,7 +224,7 @@ func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object,
|
|||
case p := <-panicCh:
|
||||
panic(p)
|
||||
case <-time.After(timeout):
|
||||
return nil, errors.NewTimeoutError("request did not complete within allowed duration", 0)
|
||||
return nil, errors.NewTimeoutError(fmt.Sprintf("request did not complete within requested timeout %s", timeout), 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,23 +289,26 @@ func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) err
|
|||
return nil
|
||||
}
|
||||
|
||||
// setListSelfLink sets the self link of a list to the base URL, then sets the self links
|
||||
// on all child objects returned. Returns the number of items in the list.
|
||||
func setListSelfLink(obj runtime.Object, ctx context.Context, req *http.Request, namer ScopeNamer) (int, error) {
|
||||
// setObjectSelfLink sets the self link of an object as needed.
|
||||
func setObjectSelfLink(ctx context.Context, obj runtime.Object, req *http.Request, namer ScopeNamer) error {
|
||||
if !meta.IsListType(obj) {
|
||||
return 0, nil
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
return fmt.Errorf("missing requestInfo")
|
||||
}
|
||||
return setSelfLink(obj, requestInfo, namer)
|
||||
}
|
||||
|
||||
uri, err := namer.GenerateListLink(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
if err := namer.SetSelfLink(obj, uri); err != nil {
|
||||
glog.V(4).Infof("Unable to set self link on object: %v", err)
|
||||
klog.V(4).Infof("Unable to set self link on object: %v", err)
|
||||
}
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("missing requestInfo")
|
||||
return fmt.Errorf("missing requestInfo")
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
|
@ -289,7 +316,14 @@ func setListSelfLink(obj runtime.Object, ctx context.Context, req *http.Request,
|
|||
count++
|
||||
return setSelfLink(obj, requestInfo, namer)
|
||||
})
|
||||
return count, err
|
||||
|
||||
if count == 0 {
|
||||
if err := meta.SetList(obj, []runtime.Object{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func summarizeData(data []byte, maxLength int) string {
|
||||
|
|
@ -309,9 +343,23 @@ func summarizeData(data []byte, maxLength int) string {
|
|||
}
|
||||
}
|
||||
|
||||
func readBody(req *http.Request) ([]byte, error) {
|
||||
func limitedReadBody(req *http.Request, limit int64) ([]byte, error) {
|
||||
defer req.Body.Close()
|
||||
return ioutil.ReadAll(req.Body)
|
||||
if limit <= 0 {
|
||||
return ioutil.ReadAll(req.Body)
|
||||
}
|
||||
lr := &io.LimitedReader{
|
||||
R: req.Body,
|
||||
N: limit + 1,
|
||||
}
|
||||
data, err := ioutil.ReadAll(lr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lr.N <= 0 {
|
||||
return nil, errors.NewRequestEntityTooLargeError(fmt.Sprintf("limit is %d", limit))
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func parseTimeout(str string) time.Duration {
|
||||
|
|
@ -320,7 +368,7 @@ func parseTimeout(str string) time.Duration {
|
|||
if err == nil {
|
||||
return timeout
|
||||
}
|
||||
glog.Errorf("Failed to parse %q: %v", str, err)
|
||||
klog.Errorf("Failed to parse %q: %v", str, err)
|
||||
}
|
||||
return 30 * time.Second
|
||||
}
|
||||
|
|
|
|||
138
vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
generated
vendored
138
vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
generated
vendored
|
|
@ -20,17 +20,25 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utiltrace "k8s.io/apiserver/pkg/util/trace"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// UpdateResource returns a function that will handle a resource update
|
||||
|
|
@ -40,8 +48,8 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
trace := utiltrace.New("Update " + req.URL.Path)
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
if isDryRun(req.URL) {
|
||||
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||
if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
|
||||
scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -56,12 +64,30 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
ctx := req.Context()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
body, err := readBody(req)
|
||||
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
options := &metav1.UpdateOptions{}
|
||||
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
||||
err = errors.NewBadRequest(err.Error())
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
if errs := validation.ValidateUpdateOptions(options); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "UpdateOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
|
|
@ -69,8 +95,9 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
}
|
||||
defaultGVK := scope.Kind
|
||||
original := r.New()
|
||||
|
||||
trace.Step("About to convert to expected version")
|
||||
decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: defaultGVK.Group, Version: runtime.APIVersionInternal})
|
||||
decoder := scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion)
|
||||
obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
|
||||
if err != nil {
|
||||
err = transformDecodeError(scope.Typer, err, original, gvk, body)
|
||||
|
|
@ -94,13 +121,47 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
}
|
||||
|
||||
userInfo, _ := request.UserFrom(ctx)
|
||||
staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)
|
||||
var transformers []rest.TransformFunc
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Update) {
|
||||
transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
|
||||
transformers := []rest.TransformFunc{}
|
||||
if scope.FieldManager != nil {
|
||||
transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
|
||||
obj, err := scope.FieldManager.Update(liveObj, newObj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err)
|
||||
}
|
||||
return obj, nil
|
||||
})
|
||||
}
|
||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
|
||||
transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
|
||||
isNotZeroObject, err := hasUID(oldObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error when extracting UID from oldObj: %v", err.Error())
|
||||
} else if !isNotZeroObject {
|
||||
if mutatingAdmission.Handles(admission.Create) {
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo), &scope)
|
||||
}
|
||||
} else {
|
||||
if mutatingAdmission.Handles(admission.Update) {
|
||||
return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo), &scope)
|
||||
}
|
||||
}
|
||||
return newObj, nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
createAuthorizerAttributes := authorizer.AttributesRecord{
|
||||
User: userInfo,
|
||||
ResourceRequest: true,
|
||||
Path: req.URL.Path,
|
||||
Verb: "create",
|
||||
APIGroup: scope.Resource.Group,
|
||||
APIVersion: scope.Resource.Version,
|
||||
Resource: scope.Resource.Resource,
|
||||
Subresource: scope.Subresource,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
trace.Step("About to store object in database")
|
||||
wasCreated := false
|
||||
|
|
@ -109,8 +170,15 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
ctx,
|
||||
name,
|
||||
rest.DefaultUpdatedObjectInfo(obj, transformers...),
|
||||
rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
|
||||
rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
|
||||
withAuthorization(rest.AdmissionToValidateObjectFunc(
|
||||
admit,
|
||||
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo), &scope),
|
||||
scope.Authorizer, createAuthorizerAttributes),
|
||||
rest.AdmissionToValidateObjectUpdateFunc(
|
||||
admit,
|
||||
admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo), &scope),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
wasCreated = created
|
||||
return obj, err
|
||||
|
|
@ -121,22 +189,44 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||
}
|
||||
trace.Step("Object stored in database")
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("missing requestInfo"), w, req)
|
||||
return
|
||||
}
|
||||
if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
trace.Step("Self-link added")
|
||||
|
||||
status := http.StatusOK
|
||||
if wasCreated {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
|
||||
transformResponseObject(ctx, scope, req, w, status, result)
|
||||
scope.Trace = trace
|
||||
transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
|
||||
}
|
||||
}
|
||||
|
||||
func withAuthorization(validate rest.ValidateObjectFunc, a authorizer.Authorizer, attributes authorizer.Attributes) rest.ValidateObjectFunc {
|
||||
var once sync.Once
|
||||
var authorizerDecision authorizer.Decision
|
||||
var authorizerReason string
|
||||
var authorizerErr error
|
||||
return func(obj runtime.Object) error {
|
||||
if a == nil {
|
||||
return errors.NewInternalError(fmt.Errorf("no authorizer provided, unable to authorize a create on update"))
|
||||
}
|
||||
once.Do(func() {
|
||||
authorizerDecision, authorizerReason, authorizerErr = a.Authorize(attributes)
|
||||
})
|
||||
// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
|
||||
if authorizerDecision == authorizer.DecisionAllow {
|
||||
// Continue to validating admission
|
||||
return validate(obj)
|
||||
}
|
||||
if authorizerErr != nil {
|
||||
return errors.NewInternalError(authorizerErr)
|
||||
}
|
||||
|
||||
// The user is not authorized to perform this action, so we need to build the error response
|
||||
gr := schema.GroupResource{
|
||||
Group: attributes.GetAPIGroup(),
|
||||
Resource: attributes.GetResource(),
|
||||
}
|
||||
name := attributes.GetName()
|
||||
err := fmt.Errorf("%v", authorizerReason)
|
||||
return errors.NewForbidden(gr, name, err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
generated
vendored
Executable file → Normal file
10
vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
generated
vendored
Executable file → Normal file
|
|
@ -209,7 +209,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
s.Fixup(obj)
|
||||
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||
// unexpected error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -223,14 +223,14 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
// and we get the benefit of using conversion functions which already have to stay in sync
|
||||
outEvent := &metav1.WatchEvent{}
|
||||
*internalEvent = metav1.InternalEvent(event)
|
||||
err := metav1.Convert_versioned_InternalEvent_to_versioned_Event(internalEvent, outEvent, nil)
|
||||
err := metav1.Convert_v1_InternalEvent_To_v1_WatchEvent(internalEvent, outEvent, nil)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to convert watch object: %v", err))
|
||||
// client disconnect.
|
||||
return
|
||||
}
|
||||
if err := e.Encode(outEvent); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v (%#v)", err, e))
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v (%#v)", outEvent, err, e))
|
||||
// client disconnect.
|
||||
return
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) {
|
|||
s.Fixup(obj)
|
||||
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||
// unexpected error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -291,7 +291,7 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) {
|
|||
// and we get the benefit of using conversion functions which already have to stay in sync
|
||||
outEvent := &metav1.WatchEvent{}
|
||||
*internalEvent = metav1.InternalEvent(event)
|
||||
err := metav1.Convert_versioned_InternalEvent_to_versioned_Event(internalEvent, outEvent, nil)
|
||||
err := metav1.Convert_v1_InternalEvent_To_v1_WatchEvent(internalEvent, outEvent, nil)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to convert watch object: %v", err))
|
||||
// client disconnect.
|
||||
|
|
|
|||
186
vendor/k8s.io/apiserver/pkg/endpoints/installer.go
generated
vendored
186
vendor/k8s.io/apiserver/pkg/endpoints/installer.go
generated
vendored
|
|
@ -27,20 +27,21 @@ import (
|
|||
"unicode"
|
||||
|
||||
restful "github.com/emicklei/go-restful"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"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/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"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericfilters "k8s.io/apiserver/pkg/server/filters"
|
||||
utilopenapi "k8s.io/apiserver/pkg/util/openapi"
|
||||
openapibuilder "k8s.io/kube-openapi/pkg/builder"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -133,17 +134,31 @@ func (a *APIInstaller) newWebService() *restful.WebService {
|
|||
return ws
|
||||
}
|
||||
|
||||
// getResourceKind returns the external group version kind registered for the given storage
|
||||
// calculate the storage gvk, the gvk objects are converted to before persisted to the etcd.
|
||||
func getStorageVersionKind(storageVersioner runtime.GroupVersioner, storage rest.Storage, typer runtime.ObjectTyper) (schema.GroupVersionKind, error) {
|
||||
object := storage.New()
|
||||
fqKinds, _, err := typer.ObjectKinds(object)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, err
|
||||
}
|
||||
gvk, ok := storageVersioner.KindForGroupVersionKinds(fqKinds)
|
||||
if !ok {
|
||||
return schema.GroupVersionKind{}, fmt.Errorf("cannot find the storage version kind for %v", reflect.TypeOf(object))
|
||||
}
|
||||
return gvk, nil
|
||||
}
|
||||
|
||||
// GetResourceKind returns the external group version kind registered for the given storage
|
||||
// object. If the storage object is a subresource and has an override supplied for it, it returns
|
||||
// the group version kind supplied in the override.
|
||||
func (a *APIInstaller) getResourceKind(path string, storage rest.Storage) (schema.GroupVersionKind, error) {
|
||||
func GetResourceKind(groupVersion schema.GroupVersion, storage rest.Storage, typer runtime.ObjectTyper) (schema.GroupVersionKind, error) {
|
||||
// Let the storage tell us exactly what GVK it has
|
||||
if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok {
|
||||
return gvkProvider.GroupVersionKind(a.group.GroupVersion), nil
|
||||
return gvkProvider.GroupVersionKind(groupVersion), nil
|
||||
}
|
||||
|
||||
object := storage.New()
|
||||
fqKinds, _, err := a.group.Typer.ObjectKinds(object)
|
||||
fqKinds, _, err := typer.ObjectKinds(object)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, err
|
||||
}
|
||||
|
|
@ -152,13 +167,13 @@ func (a *APIInstaller) getResourceKind(path string, storage rest.Storage) (schem
|
|||
// we're trying to register here
|
||||
fqKindToRegister := schema.GroupVersionKind{}
|
||||
for _, fqKind := range fqKinds {
|
||||
if fqKind.Group == a.group.GroupVersion.Group {
|
||||
fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
|
||||
if fqKind.Group == groupVersion.Group {
|
||||
fqKindToRegister = groupVersion.WithKind(fqKind.Kind)
|
||||
break
|
||||
}
|
||||
}
|
||||
if fqKindToRegister.Empty() {
|
||||
return schema.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion)
|
||||
return schema.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, groupVersion)
|
||||
}
|
||||
|
||||
// group is guaranteed to match based on the check above
|
||||
|
|
@ -178,7 +193,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
return nil, err
|
||||
}
|
||||
|
||||
fqKindToRegister, err := a.getResourceKind(path, storage)
|
||||
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
|
||||
}
|
||||
|
|
@ -225,6 +242,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
watcher, isWatcher := storage.(rest.Watcher)
|
||||
connecter, isConnecter := storage.(rest.Connecter)
|
||||
storageMeta, isMetadata := storage.(rest.StorageMetadata)
|
||||
storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)
|
||||
if !isMetadata {
|
||||
storageMeta = defaultStorageMetadata{}
|
||||
}
|
||||
|
|
@ -260,6 +278,18 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedCreateOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("CreateOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedPatchOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("PatchOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedUpdateOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("UpdateOptions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var versionedDeleteOptions runtime.Object
|
||||
var versionedDeleterObject interface{}
|
||||
|
|
@ -351,6 +381,17 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
tableProvider, _ := storage.(rest.TableConvertor)
|
||||
|
||||
var apiResource metav1.APIResource
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionHash) &&
|
||||
isStorageVersionProvider &&
|
||||
storageVersionProvider.StorageVersion() != nil {
|
||||
versioner := storageVersionProvider.StorageVersion()
|
||||
gvk, err := getStorageVersionKind(versioner, storage, a.group.Typer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiResource.StorageVersionHash = discovery.StorageVersionHash(gvk.Group, gvk.Version, gvk.Kind)
|
||||
}
|
||||
|
||||
// Get the list of actions for the given scope.
|
||||
switch {
|
||||
case !namespaceScoped:
|
||||
|
|
@ -382,7 +423,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
|
||||
actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
|
||||
actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
|
||||
// DEPRECATED
|
||||
// DEPRECATED in 1.11
|
||||
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList)
|
||||
|
||||
// Add actions at the item path: /api/apiVersion/resource/{name}
|
||||
|
|
@ -393,15 +434,15 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
|
||||
actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
|
||||
// DEPRECATED in 1.11
|
||||
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher)
|
||||
actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter)
|
||||
actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath)
|
||||
break
|
||||
default:
|
||||
namespaceParamName := "namespaces"
|
||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||
namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string")
|
||||
namespacedPath := namespaceParamName + "/{" + "namespace" + "}/" + resource
|
||||
namespacedPath := namespaceParamName + "/{namespace}/" + resource
|
||||
namespaceParams := []*restful.Parameter{namespaceParam}
|
||||
|
||||
resourcePath := namespacedPath
|
||||
|
|
@ -429,7 +470,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
|
||||
actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
|
||||
actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
|
||||
// DEPRECATED
|
||||
// DEPRECATED in 1.11
|
||||
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList)
|
||||
|
||||
actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter)
|
||||
|
|
@ -439,6 +480,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
|
||||
actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
|
||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
|
||||
// DEPRECATED in 1.11
|
||||
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher)
|
||||
actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter)
|
||||
actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath)
|
||||
|
|
@ -448,9 +490,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
// TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete)
|
||||
if !isSubresource {
|
||||
actions = appendIf(actions, action{"LIST", resource, params, namer, true}, isLister)
|
||||
// DEPRECATED in 1.11
|
||||
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer, true}, allowWatchList)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Create Routes for the actions.
|
||||
|
|
@ -483,6 +525,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Defaulter: a.group.Defaulter,
|
||||
Typer: a.group.Typer,
|
||||
UnsafeConvertor: a.group.UnsafeConvertor,
|
||||
Authorizer: a.group.Authorizer,
|
||||
|
||||
// TODO: Check for the interface on storage
|
||||
TableConvertor: tableProvider,
|
||||
|
|
@ -492,20 +535,27 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Subresource: subresource,
|
||||
Kind: fqKindToRegister,
|
||||
|
||||
HubGroupVersion: schema.GroupVersion{Group: fqKindToRegister.Group, Version: runtime.APIVersionInternal},
|
||||
|
||||
MetaGroupVersion: metav1.SchemeGroupVersion,
|
||||
|
||||
MaxRequestBodyBytes: a.group.MaxRequestBodyBytes,
|
||||
}
|
||||
if a.group.MetaGroupVersion != nil {
|
||||
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
|
||||
}
|
||||
if a.group.OpenAPIConfig != nil {
|
||||
openAPIDefinitions, err := openapibuilder.BuildOpenAPIDefinitionsForResource(defaultVersionedObject, a.group.OpenAPIConfig)
|
||||
if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
fm, err := fieldmanager.NewFieldManager(
|
||||
a.group.OpenAPIModels,
|
||||
a.group.UnsafeConvertor,
|
||||
a.group.Defaulter,
|
||||
fqKindToRegister.GroupVersion(),
|
||||
reqScope.HubGroupVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to build openapi definitions for %v: %v", fqKindToRegister, err)
|
||||
}
|
||||
reqScope.OpenAPISchema, err = utilopenapi.ToProtoSchema(openAPIDefinitions, fqKindToRegister)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get openapi schema for %v: %v", fqKindToRegister, err)
|
||||
return nil, fmt.Errorf("failed to create field manager: %v", err)
|
||||
}
|
||||
reqScope.FieldManager = fm
|
||||
}
|
||||
for _, action := range actions {
|
||||
producedObject := storageMeta.ProducesObject(action.Verb)
|
||||
|
|
@ -548,7 +598,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
return nil, fmt.Errorf("missing parent storage: %q", resource)
|
||||
}
|
||||
|
||||
fqParentKind, err := a.getResourceKind(resource, parentStorage)
|
||||
fqParentKind, err := GetResourceKind(a.group.GroupVersion, parentStorage, a.group.Typer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -568,9 +618,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
|
||||
if needOverride {
|
||||
// need change the reported verb
|
||||
handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), resource, subresource, requestScope, handler)
|
||||
handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler)
|
||||
} else {
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, handler)
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler)
|
||||
}
|
||||
|
||||
if a.enableAPIResponseCompression {
|
||||
|
|
@ -588,12 +638,12 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Returns(http.StatusOK, "OK", producedObject).
|
||||
Writes(producedObject)
|
||||
if isGetterWithOptions {
|
||||
if err := addObjectParams(ws, route, versionedGetOptions); err != nil {
|
||||
if err := AddObjectParams(ws, route, versionedGetOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if isExporter {
|
||||
if err := addObjectParams(ws, route, versionedExportOptions); err != nil {
|
||||
if err := AddObjectParams(ws, route, versionedExportOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -604,7 +654,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
if isSubresource {
|
||||
doc = "list " + subresource + " of objects of kind " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
|
||||
if a.enableAPIResponseCompression {
|
||||
handler = genericfilters.RestfulWithCompression(handler)
|
||||
}
|
||||
|
|
@ -615,7 +665,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), allMediaTypes...)...).
|
||||
Returns(http.StatusOK, "OK", versionedList).
|
||||
Writes(versionedList)
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
|
|
@ -639,7 +689,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
if isSubresource {
|
||||
doc = "replace " + subresource + " of the specified " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulUpdateResource(updater, reqScope, admit))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulUpdateResource(updater, reqScope, admit))
|
||||
route := ws.PUT(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
|
|
@ -651,6 +701,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Returns(http.StatusCreated, "Created", producedObject).
|
||||
Reads(defaultVersionedObject).
|
||||
Writes(producedObject)
|
||||
if err := AddObjectParams(ws, route, versionedUpdateOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
case "PATCH": // Partially update a resource
|
||||
|
|
@ -663,16 +716,22 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
string(types.MergePatchType),
|
||||
string(types.StrategicMergePatchType),
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulPatchResource(patcher, reqScope, admit, supportedTypes))
|
||||
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))
|
||||
route := ws.PATCH(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Consumes(string(types.JSONPatchType), string(types.MergePatchType), string(types.StrategicMergePatchType)).
|
||||
Consumes(supportedTypes...).
|
||||
Operation("patch"+namespaced+kind+strings.Title(subresource)+operationSuffix).
|
||||
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
|
||||
Returns(http.StatusOK, "OK", producedObject).
|
||||
Reads(metav1.Patch{}).
|
||||
Writes(producedObject)
|
||||
if err := AddObjectParams(ws, route, versionedPatchOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
case "POST": // Create a resource.
|
||||
|
|
@ -682,8 +741,8 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
} else {
|
||||
handler = restfulCreateResource(creater, reqScope, admit)
|
||||
}
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, handler)
|
||||
article := getArticleForNoun(kind, " ")
|
||||
handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler)
|
||||
article := GetArticleForNoun(kind, " ")
|
||||
doc := "create" + article + kind
|
||||
if isSubresource {
|
||||
doc = "create " + subresource + " of" + article + kind
|
||||
|
|
@ -700,25 +759,30 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Returns(http.StatusAccepted, "Accepted", producedObject).
|
||||
Reads(defaultVersionedObject).
|
||||
Writes(producedObject)
|
||||
if err := AddObjectParams(ws, route, versionedCreateOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
case "DELETE": // Delete a resource.
|
||||
article := getArticleForNoun(kind, " ")
|
||||
article := GetArticleForNoun(kind, " ")
|
||||
doc := "delete" + article + kind
|
||||
if isSubresource {
|
||||
doc = "delete " + subresource + " of" + article + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
|
||||
route := ws.DELETE(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Operation("delete"+namespaced+kind+strings.Title(subresource)+operationSuffix).
|
||||
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
|
||||
Writes(versionedStatus).
|
||||
Returns(http.StatusOK, "OK", versionedStatus)
|
||||
Returns(http.StatusOK, "OK", versionedStatus).
|
||||
Returns(http.StatusAccepted, "Accepted", versionedStatus)
|
||||
if isGracefulDeleter {
|
||||
route.Reads(versionedDeleterObject)
|
||||
if err := addObjectParams(ws, route, versionedDeleteOptions); err != nil {
|
||||
route.ParameterNamed("body").Required(false)
|
||||
if err := AddObjectParams(ws, route, versionedDeleteOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -729,7 +793,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
if isSubresource {
|
||||
doc = "delete collection of " + subresource + " of a " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
|
||||
route := ws.DELETE(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
|
|
@ -737,18 +801,19 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
|
||||
Writes(versionedStatus).
|
||||
Returns(http.StatusOK, "OK", versionedStatus)
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
// TODO: deprecated
|
||||
// deprecated in 1.11
|
||||
case "WATCH": // Watch a resource.
|
||||
doc := "watch changes to an object of kind " + kind
|
||||
if isSubresource {
|
||||
doc = "watch changes to " + subresource + " of an object of kind " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||
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))
|
||||
route := ws.GET(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
|
|
@ -756,18 +821,19 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Produces(allMediaTypes...).
|
||||
Returns(http.StatusOK, "OK", versionedWatchEvent).
|
||||
Writes(versionedWatchEvent)
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
// TODO: deprecated
|
||||
// deprecated in 1.11
|
||||
case "WATCHLIST": // Watch all resources of a kind.
|
||||
doc := "watch individual changes to a list of " + kind
|
||||
if isSubresource {
|
||||
doc = "watch individual changes to a list of " + subresource + " of " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||
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))
|
||||
route := ws.GET(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
|
|
@ -775,7 +841,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Produces(allMediaTypes...).
|
||||
Returns(http.StatusOK, "OK", versionedWatchEvent).
|
||||
Writes(versionedWatchEvent)
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
if err := AddObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
|
|
@ -790,7 +856,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
if isSubresource {
|
||||
doc = "connect " + method + " requests to " + subresource + " of " + kind
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulConnectResource(connecter, reqScope, admit, path, isSubresource))
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulConnectResource(connecter, reqScope, admit, path, isSubresource))
|
||||
route := ws.Method(method).Path(action.Path).
|
||||
To(handler).
|
||||
Doc(doc).
|
||||
|
|
@ -799,12 +865,19 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||
Consumes("*/*").
|
||||
Writes(connectProducedObject)
|
||||
if versionedConnectOptions != nil {
|
||||
if err := addObjectParams(ws, route, versionedConnectOptions); err != nil {
|
||||
if err := AddObjectParams(ws, route, versionedConnectOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
routes = append(routes, route)
|
||||
|
||||
// transform ConnectMethods to kube verbs
|
||||
if kubeVerb, found := toDiscoveryKubeVerb[method]; found {
|
||||
if len(kubeVerb) != 0 {
|
||||
kubeVerbs[kubeVerb] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized action verb: %s", action.Verb)
|
||||
|
|
@ -855,26 +928,19 @@ func appendIf(actions []action, a action, shouldAppend bool) []action {
|
|||
return actions
|
||||
}
|
||||
|
||||
// Wraps a http.Handler function inside a restful.RouteFunction
|
||||
func routeFunction(handler http.Handler) restful.RouteFunction {
|
||||
return func(restReq *restful.Request, restResp *restful.Response) {
|
||||
handler.ServeHTTP(restResp.ResponseWriter, restReq.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
|
||||
for _, param := range params {
|
||||
route.Param(param)
|
||||
}
|
||||
}
|
||||
|
||||
// addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
|
||||
// AddObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
|
||||
// The object must be a pointer to a struct; only fields at the top level of the struct that are not
|
||||
// themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard
|
||||
// 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{}) error {
|
||||
sv, err := conversion.EnforcePtr(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -976,8 +1042,8 @@ func splitSubresource(path string) (string, string, error) {
|
|||
return resource, subresource, nil
|
||||
}
|
||||
|
||||
// getArticleForNoun returns the article needed for the given noun.
|
||||
func getArticleForNoun(noun string, padding string) string {
|
||||
// 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" {
|
||||
// Plurals don't have an article.
|
||||
// Don't catch words like class
|
||||
|
|
|
|||
2
vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS
generated
vendored
Executable file → Normal file
2
vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS
generated
vendored
Executable file → Normal file
|
|
@ -1,3 +1,5 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- wojtek-t
|
||||
- jimmidyson
|
||||
|
|
|
|||
118
vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
generated
vendored
118
vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
generated
vendored
|
|
@ -26,8 +26,13 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
utilsets "k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
|
@ -40,59 +45,93 @@ type resettableCollector interface {
|
|||
Reset()
|
||||
}
|
||||
|
||||
const (
|
||||
APIServerComponent string = "apiserver"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO(a-robinson): Add unit tests for the handling of these metrics once
|
||||
// the upstream library supports it.
|
||||
requestCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "apiserver_request_count",
|
||||
Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.",
|
||||
Name: "apiserver_request_total",
|
||||
Help: "Counter of apiserver requests broken out for each verb, dry run value, group, version, resource, scope, component, client, and HTTP response contentType and code.",
|
||||
},
|
||||
[]string{"verb", "resource", "subresource", "scope", "client", "contentType", "code"},
|
||||
// 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", "client", "contentType", "code"},
|
||||
)
|
||||
deprecatedRequestCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "apiserver_request_count",
|
||||
Help: "(Deprecated) Counter of apiserver requests broken out for each verb, group, version, resource, scope, component, client, and HTTP response contentType and code.",
|
||||
},
|
||||
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component", "client", "contentType", "code"},
|
||||
)
|
||||
longRunningRequestGauge = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "apiserver_longrunning_gauge",
|
||||
Help: "Gauge of all active long-running apiserver requests broken out by verb, API resource, and scope. Not all requests are tracked this way.",
|
||||
Help: "Gauge of all active long-running apiserver requests broken out by verb, group, version, resource, scope and component. Not all requests are tracked this way.",
|
||||
},
|
||||
[]string{"verb", "resource", "subresource", "scope"},
|
||||
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||
)
|
||||
requestLatencies = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "apiserver_request_duration_seconds",
|
||||
Help: "Response latency distribution in seconds for each verb, dry run value, group, version, resource, subresource, scope and component.",
|
||||
// This metric is used for verifying api call latencies SLO,
|
||||
// as well as tracking regressions in this aspects.
|
||||
// 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},
|
||||
},
|
||||
[]string{"verb", "dry_run", "group", "version", "resource", "subresource", "scope", "component"},
|
||||
)
|
||||
deprecatedRequestLatencies = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "apiserver_request_latencies",
|
||||
Help: "Response latency distribution in microseconds for each verb, resource and subresource.",
|
||||
Help: "(Deprecated) Response latency distribution in microseconds for each verb, group, version, resource, subresource, scope and component.",
|
||||
// Use buckets ranging from 125 ms to 8 seconds.
|
||||
Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
|
||||
},
|
||||
[]string{"verb", "resource", "subresource", "scope"},
|
||||
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||
)
|
||||
requestLatenciesSummary = prometheus.NewSummaryVec(
|
||||
deprecatedRequestLatenciesSummary = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "apiserver_request_latencies_summary",
|
||||
Help: "Response latency summary in microseconds for each verb, resource and subresource.",
|
||||
Help: "(Deprecated) Response latency summary in microseconds for each verb, group, version, resource, subresource, scope and component.",
|
||||
// Make the sliding window of 5h.
|
||||
// TODO: The value for this should be based on our SLI definition (medium term).
|
||||
MaxAge: 5 * time.Hour,
|
||||
},
|
||||
[]string{"verb", "resource", "subresource", "scope"},
|
||||
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||
)
|
||||
responseSizes = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "apiserver_response_sizes",
|
||||
Help: "Response size distribution in bytes for each verb, resource, subresource and scope (namespace/cluster).",
|
||||
Help: "Response size distribution in bytes for each group, version, verb, resource, subresource, scope and component.",
|
||||
// Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB).
|
||||
Buckets: prometheus.ExponentialBuckets(1000, 10.0, 7),
|
||||
},
|
||||
[]string{"verb", "resource", "subresource", "scope"},
|
||||
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||
)
|
||||
// DroppedRequests is a number of requests dropped with 'Try again later' response"
|
||||
DroppedRequests = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "apiserver_dropped_requests",
|
||||
Name: "apiserver_dropped_requests_total",
|
||||
Help: "Number of requests dropped with 'Try again later' response",
|
||||
},
|
||||
[]string{"requestKind"},
|
||||
)
|
||||
DeprecatedDroppedRequests = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "apiserver_dropped_requests",
|
||||
Help: "(Deprecated) Number of requests dropped with 'Try again later' response",
|
||||
},
|
||||
[]string{"requestKind"},
|
||||
)
|
||||
// RegisteredWatchers is a number of currently registered watchers splitted by resource.
|
||||
RegisteredWatchers = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
|
|
@ -106,7 +145,7 @@ var (
|
|||
currentInflightRequests = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "apiserver_current_inflight_requests",
|
||||
Help: "Maximal mumber of currently used inflight request limit of this apiserver per request kind in last second.",
|
||||
Help: "Maximal number of currently used inflight request limit of this apiserver per request kind in last second.",
|
||||
},
|
||||
[]string{"requestKind"},
|
||||
)
|
||||
|
|
@ -114,11 +153,14 @@ var (
|
|||
|
||||
metrics = []resettableCollector{
|
||||
requestCounter,
|
||||
deprecatedRequestCounter,
|
||||
longRunningRequestGauge,
|
||||
requestLatencies,
|
||||
requestLatenciesSummary,
|
||||
deprecatedRequestLatencies,
|
||||
deprecatedRequestLatenciesSummary,
|
||||
responseSizes,
|
||||
DroppedRequests,
|
||||
DeprecatedDroppedRequests,
|
||||
RegisteredWatchers,
|
||||
currentInflightRequests,
|
||||
}
|
||||
|
|
@ -157,21 +199,21 @@ func UpdateInflightRequestMetrics(nonmutating, mutating int) {
|
|||
// Record records a single request to the standard metrics endpoints. For use by handlers that perform their own
|
||||
// processing. All API paths should use InstrumentRouteFunc implicitly. Use this instead of MonitorRequest if
|
||||
// you already have a RequestInfo object.
|
||||
func Record(req *http.Request, requestInfo *request.RequestInfo, contentType string, code int, responseSizeInBytes int, elapsed time.Duration) {
|
||||
func Record(req *http.Request, requestInfo *request.RequestInfo, component, contentType string, code int, responseSizeInBytes int, elapsed time.Duration) {
|
||||
if requestInfo == nil {
|
||||
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||
}
|
||||
scope := CleanScope(requestInfo)
|
||||
if requestInfo.IsResourceRequest {
|
||||
MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, contentType, scope, code, responseSizeInBytes, elapsed)
|
||||
MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component, contentType, code, responseSizeInBytes, elapsed)
|
||||
} else {
|
||||
MonitorRequest(req, strings.ToUpper(requestInfo.Verb), "", requestInfo.Path, contentType, scope, code, responseSizeInBytes, elapsed)
|
||||
MonitorRequest(req, strings.ToUpper(requestInfo.Verb), "", "", "", requestInfo.Path, scope, component, contentType, code, responseSizeInBytes, elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
// RecordLongRunning tracks the execution of a long running request against the API server. It provides an accurate count
|
||||
// of the total number of open long running requests. requestInfo may be nil if the caller is not in the normal request flow.
|
||||
func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, fn func()) {
|
||||
func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, component string, fn func()) {
|
||||
if requestInfo == nil {
|
||||
requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
|
||||
}
|
||||
|
|
@ -179,9 +221,9 @@ func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, fn f
|
|||
scope := CleanScope(requestInfo)
|
||||
reportedVerb := cleanVerb(strings.ToUpper(requestInfo.Verb), req)
|
||||
if requestInfo.IsResourceRequest {
|
||||
g = longRunningRequestGauge.WithLabelValues(reportedVerb, requestInfo.Resource, requestInfo.Subresource, scope)
|
||||
g = longRunningRequestGauge.WithLabelValues(reportedVerb, requestInfo.APIGroup, requestInfo.APIVersion, requestInfo.Resource, requestInfo.Subresource, scope, component)
|
||||
} else {
|
||||
g = longRunningRequestGauge.WithLabelValues(reportedVerb, "", requestInfo.Path, scope)
|
||||
g = longRunningRequestGauge.WithLabelValues(reportedVerb, "", "", "", requestInfo.Path, scope, component)
|
||||
}
|
||||
g.Inc()
|
||||
defer g.Dec()
|
||||
|
|
@ -190,22 +232,26 @@ func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, fn f
|
|||
|
||||
// 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, resource, subresource, scope, contentType string, httpCode, respSize int, elapsed time.Duration) {
|
||||
func MonitorRequest(req *http.Request, verb, group, version, resource, subresource, scope, component, contentType string, httpCode, respSize int, elapsed time.Duration) {
|
||||
reportedVerb := cleanVerb(verb, req)
|
||||
dryRun := cleanDryRun(req.URL.Query()["dryRun"])
|
||||
client := cleanUserAgent(utilnet.GetHTTPClient(req))
|
||||
elapsedMicroseconds := float64(elapsed / time.Microsecond)
|
||||
requestCounter.WithLabelValues(reportedVerb, resource, subresource, scope, client, contentType, codeToString(httpCode)).Inc()
|
||||
requestLatencies.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(elapsedMicroseconds)
|
||||
requestLatenciesSummary.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(elapsedMicroseconds)
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
requestCounter.WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component, client, contentType, codeToString(httpCode)).Inc()
|
||||
deprecatedRequestCounter.WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component, client, contentType, codeToString(httpCode)).Inc()
|
||||
requestLatencies.WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component).Observe(elapsedSeconds)
|
||||
deprecatedRequestLatencies.WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(elapsedMicroseconds)
|
||||
deprecatedRequestLatenciesSummary.WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(elapsedMicroseconds)
|
||||
// We are only interested in response sizes of read requests.
|
||||
if verb == "GET" || verb == "LIST" {
|
||||
responseSizes.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(float64(respSize))
|
||||
responseSizes.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, resource, subresource, scope string, routeFunc restful.RouteFunction) restful.RouteFunction {
|
||||
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()
|
||||
|
||||
|
|
@ -224,12 +270,12 @@ func InstrumentRouteFunc(verb, resource, subresource, scope string, routeFunc re
|
|||
|
||||
routeFunc(request, response)
|
||||
|
||||
MonitorRequest(request.Request, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
|
||||
MonitorRequest(request.Request, verb, group, version, resource, subresource, scope, component, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentHandlerFunc works like Prometheus' InstrumentHandlerFunc but adds some Kubernetes endpoint specific information.
|
||||
func InstrumentHandlerFunc(verb, resource, subresource, scope string, handler http.HandlerFunc) http.HandlerFunc {
|
||||
func InstrumentHandlerFunc(verb, group, version, resource, subresource, scope, component string, handler http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
now := time.Now()
|
||||
|
||||
|
|
@ -246,7 +292,7 @@ func InstrumentHandlerFunc(verb, resource, subresource, scope string, handler ht
|
|||
|
||||
handler(w, req)
|
||||
|
||||
MonitorRequest(req, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
|
||||
MonitorRequest(req, verb, group, version, resource, subresource, scope, component, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,9 +325,21 @@ func cleanVerb(verb string, request *http.Request) string {
|
|||
if verb == "WATCHLIST" {
|
||||
reportedVerb = "WATCH"
|
||||
}
|
||||
if verb == "PATCH" && request.Header.Get("Content-Type") == string(types.ApplyPatchType) && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
reportedVerb = "APPLY"
|
||||
}
|
||||
return reportedVerb
|
||||
}
|
||||
|
||||
func cleanDryRun(dryRun []string) string {
|
||||
if errs := validation.ValidateDryRun(nil, dryRun); len(errs) > 0 {
|
||||
return "invalid"
|
||||
}
|
||||
// Since dryRun could be valid with any arbitrarily long length
|
||||
// we have to dedup and sort the elements before joining them together
|
||||
return strings.Join(utilsets.NewString(dryRun...).List(), ",")
|
||||
}
|
||||
|
||||
func cleanUserAgent(ua string) string {
|
||||
// We collapse all "web browser"-type user agents into one "browser" to reduce metric cardinality.
|
||||
if strings.HasPrefix(ua, "Mozilla/") {
|
||||
|
|
|
|||
2
vendor/k8s.io/apiserver/pkg/endpoints/openapi/OWNERS
generated
vendored
Executable file → Normal file
2
vendor/k8s.io/apiserver/pkg/endpoints/openapi/OWNERS
generated
vendored
Executable file → Normal file
|
|
@ -1,2 +1,4 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- mbohlool
|
||||
|
|
|
|||
14
vendor/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go
generated
vendored
14
vendor/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go
generated
vendored
|
|
@ -108,6 +108,18 @@ func (s groupVersionKinds) Less(i, j int) bool {
|
|||
return s[i].Group < s[j].Group
|
||||
}
|
||||
|
||||
func (s groupVersionKinds) JSON() []interface{} {
|
||||
j := []interface{}{}
|
||||
for _, gvk := range s {
|
||||
j = append(j, map[string]interface{}{
|
||||
"group": gvk.Group,
|
||||
"version": gvk.Version,
|
||||
"kind": gvk.Kind,
|
||||
})
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
// DefinitionNamer is the type to customize OpenAPI definition name.
|
||||
type DefinitionNamer struct {
|
||||
typeGroupVersionKinds map[string]groupVersionKinds
|
||||
|
|
@ -172,7 +184,7 @@ func NewDefinitionNamer(schemes ...*runtime.Scheme) *DefinitionNamer {
|
|||
func (d *DefinitionNamer) GetDefinitionName(name string) (string, spec.Extensions) {
|
||||
if groupVersionKinds, ok := d.typeGroupVersionKinds[name]; ok {
|
||||
return friendlyName(name), spec.Extensions{
|
||||
extensionGVK: []v1.GroupVersionKind(groupVersionKinds),
|
||||
extensionGVK: groupVersionKinds.JSON(),
|
||||
}
|
||||
}
|
||||
return friendlyName(name), nil
|
||||
|
|
|
|||
2
vendor/k8s.io/apiserver/pkg/endpoints/request/OWNERS
generated
vendored
Executable file → Normal file
2
vendor/k8s.io/apiserver/pkg/endpoints/request/OWNERS
generated
vendored
Executable file → Normal file
|
|
@ -1,2 +1,4 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- sttts
|
||||
|
|
|
|||
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,6 +36,9 @@ 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.
|
||||
|
|
|
|||
4
vendor/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go
generated
vendored
|
|
@ -27,7 +27,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// LongRunningRequestCheck is a predicate which is true for long-running http requests.
|
||||
|
|
@ -210,7 +210,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
|||
opts := metainternalversion.ListOptions{}
|
||||
if err := metainternalversion.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.
|
||||
glog.Errorf("Couldn't parse request %#v: %v", req.URL.Query(), err)
|
||||
klog.Errorf("Couldn't parse request %#v: %v", req.URL.Query(), err)
|
||||
// Reset opts to not rely on partial results from parsing.
|
||||
// However, if watch is set, let's report it.
|
||||
opts = metainternalversion.ListOptions{}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue