mirror of
https://github.com/kubernetes/sample-controller.git
synced 2026-08-03 00:00:02 +08:00
Merge pull request #81525 from cblecker/1.14-x/net
Update golang/x/net dependency on release-1.14 Kubernetes-commit: 8d394792b0e36316371ba90b51bfe64381e2e88a
This commit is contained in:
Generated
+265
-265
File diff suppressed because it is too large
Load Diff
+38
-8
@@ -52,10 +52,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
prefaceTimeout = 10 * time.Second
|
prefaceTimeout = 10 * time.Second
|
||||||
firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
|
firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
|
||||||
handlerChunkWriteSize = 4 << 10
|
handlerChunkWriteSize = 4 << 10
|
||||||
defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
|
defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
|
||||||
|
maxQueuedControlFrames = 10000
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -163,6 +164,15 @@ func (s *Server) maxConcurrentStreams() uint32 {
|
|||||||
return defaultMaxStreams
|
return defaultMaxStreams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maxQueuedControlFrames is the maximum number of control frames like
|
||||||
|
// SETTINGS, PING and RST_STREAM that will be queued for writing before
|
||||||
|
// the connection is closed to prevent memory exhaustion attacks.
|
||||||
|
func (s *Server) maxQueuedControlFrames() int {
|
||||||
|
// TODO: if anybody asks, add a Server field, and remember to define the
|
||||||
|
// behavior of negative values.
|
||||||
|
return maxQueuedControlFrames
|
||||||
|
}
|
||||||
|
|
||||||
type serverInternalState struct {
|
type serverInternalState struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
activeConns map[*serverConn]struct{}
|
activeConns map[*serverConn]struct{}
|
||||||
@@ -482,6 +492,7 @@ type serverConn struct {
|
|||||||
sawFirstSettings bool // got the initial SETTINGS frame after the preface
|
sawFirstSettings bool // got the initial SETTINGS frame after the preface
|
||||||
needToSendSettingsAck bool
|
needToSendSettingsAck bool
|
||||||
unackedSettings int // how many SETTINGS have we sent without ACKs?
|
unackedSettings int // how many SETTINGS have we sent without ACKs?
|
||||||
|
queuedControlFrames int // control frames in the writeSched queue
|
||||||
clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit)
|
clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit)
|
||||||
advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
|
advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
|
||||||
curClientStreams uint32 // number of open streams initiated by the client
|
curClientStreams uint32 // number of open streams initiated by the client
|
||||||
@@ -870,6 +881,14 @@ func (sc *serverConn) serve() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the peer is causing us to generate a lot of control frames,
|
||||||
|
// but not reading them from us, assume they are trying to make us
|
||||||
|
// run out of memory.
|
||||||
|
if sc.queuedControlFrames > sc.srv.maxQueuedControlFrames() {
|
||||||
|
sc.vlogf("http2: too many control frames in send queue, closing connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Start the shutdown timer after sending a GOAWAY. When sending GOAWAY
|
// Start the shutdown timer after sending a GOAWAY. When sending GOAWAY
|
||||||
// with no error code (graceful shutdown), don't start the timer until
|
// with no error code (graceful shutdown), don't start the timer until
|
||||||
// all open streams have been completed.
|
// all open streams have been completed.
|
||||||
@@ -1069,6 +1088,14 @@ func (sc *serverConn) writeFrame(wr FrameWriteRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ignoreWrite {
|
if !ignoreWrite {
|
||||||
|
if wr.isControl() {
|
||||||
|
sc.queuedControlFrames++
|
||||||
|
// For extra safety, detect wraparounds, which should not happen,
|
||||||
|
// and pull the plug.
|
||||||
|
if sc.queuedControlFrames < 0 {
|
||||||
|
sc.conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
sc.writeSched.Push(wr)
|
sc.writeSched.Push(wr)
|
||||||
}
|
}
|
||||||
sc.scheduleFrameWrite()
|
sc.scheduleFrameWrite()
|
||||||
@@ -1186,10 +1213,8 @@ func (sc *serverConn) wroteFrame(res frameWriteResult) {
|
|||||||
// If a frame is already being written, nothing happens. This will be called again
|
// If a frame is already being written, nothing happens. This will be called again
|
||||||
// when the frame is done being written.
|
// when the frame is done being written.
|
||||||
//
|
//
|
||||||
// If a frame isn't being written we need to send one, the best frame
|
// If a frame isn't being written and we need to send one, the best frame
|
||||||
// to send is selected, preferring first things that aren't
|
// to send is selected by writeSched.
|
||||||
// stream-specific (e.g. ACKing settings), and then finding the
|
|
||||||
// highest priority stream.
|
|
||||||
//
|
//
|
||||||
// If a frame isn't being written and there's nothing else to send, we
|
// If a frame isn't being written and there's nothing else to send, we
|
||||||
// flush the write buffer.
|
// flush the write buffer.
|
||||||
@@ -1217,6 +1242,9 @@ func (sc *serverConn) scheduleFrameWrite() {
|
|||||||
}
|
}
|
||||||
if !sc.inGoAway || sc.goAwayCode == ErrCodeNo {
|
if !sc.inGoAway || sc.goAwayCode == ErrCodeNo {
|
||||||
if wr, ok := sc.writeSched.Pop(); ok {
|
if wr, ok := sc.writeSched.Pop(); ok {
|
||||||
|
if wr.isControl() {
|
||||||
|
sc.queuedControlFrames--
|
||||||
|
}
|
||||||
sc.startFrameWrite(wr)
|
sc.startFrameWrite(wr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1509,6 +1537,8 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error {
|
|||||||
if err := f.ForeachSetting(sc.processSetting); err != nil {
|
if err := f.ForeachSetting(sc.processSetting); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// TODO: judging by RFC 7540, Section 6.5.3 each SETTINGS frame should be
|
||||||
|
// acknowledged individually, even if multiple are received before the ACK.
|
||||||
sc.needToSendSettingsAck = true
|
sc.needToSendSettingsAck = true
|
||||||
sc.scheduleFrameWrite()
|
sc.scheduleFrameWrite()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+7
-1
@@ -32,7 +32,7 @@ type WriteScheduler interface {
|
|||||||
|
|
||||||
// Pop dequeues the next frame to write. Returns false if no frames can
|
// Pop dequeues the next frame to write. Returns false if no frames can
|
||||||
// be written. Frames with a given wr.StreamID() are Pop'd in the same
|
// be written. Frames with a given wr.StreamID() are Pop'd in the same
|
||||||
// order they are Push'd.
|
// order they are Push'd. No frames should be discarded except by CloseStream.
|
||||||
Pop() (wr FrameWriteRequest, ok bool)
|
Pop() (wr FrameWriteRequest, ok bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +76,12 @@ func (wr FrameWriteRequest) StreamID() uint32 {
|
|||||||
return wr.stream.id
|
return wr.stream.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isControl reports whether wr is a control frame for MaxQueuedControlFrames
|
||||||
|
// purposes. That includes non-stream frames and RST_STREAM frames.
|
||||||
|
func (wr FrameWriteRequest) isControl() bool {
|
||||||
|
return wr.stream == nil
|
||||||
|
}
|
||||||
|
|
||||||
// DataSize returns the number of flow control bytes that must be consumed
|
// DataSize returns the number of flow control bytes that must be consumed
|
||||||
// to write this entire frame. This is 0 for non-DATA frames.
|
// to write this entire frame. This is 0 for non-DATA frames.
|
||||||
func (wr FrameWriteRequest) DataSize() int {
|
func (wr FrameWriteRequest) DataSize() int {
|
||||||
|
|||||||
+4
-3
@@ -74,9 +74,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
|
|||||||
KeyFile: c.KeyFile,
|
KeyFile: c.KeyFile,
|
||||||
KeyData: c.KeyData,
|
KeyData: c.KeyData,
|
||||||
},
|
},
|
||||||
Username: c.Username,
|
Username: c.Username,
|
||||||
Password: c.Password,
|
Password: c.Password,
|
||||||
BearerToken: c.BearerToken,
|
BearerToken: c.BearerToken,
|
||||||
|
BearerTokenFile: c.BearerTokenFile,
|
||||||
Impersonate: transport.ImpersonationConfig{
|
Impersonate: transport.ImpersonationConfig{
|
||||||
UserName: c.Impersonate.UserName,
|
UserName: c.Impersonate.UserName,
|
||||||
Groups: c.Impersonate.Groups,
|
Groups: c.Impersonate.Groups,
|
||||||
|
|||||||
+25
-17
@@ -48,7 +48,7 @@ type ExpirationCache struct {
|
|||||||
// ExpirationPolicy dictates when an object expires. Currently only abstracted out
|
// ExpirationPolicy dictates when an object expires. Currently only abstracted out
|
||||||
// so unittests don't rely on the system clock.
|
// so unittests don't rely on the system clock.
|
||||||
type ExpirationPolicy interface {
|
type ExpirationPolicy interface {
|
||||||
IsExpired(obj *timestampedEntry) bool
|
IsExpired(obj *TimestampedEntry) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TTLPolicy implements a ttl based ExpirationPolicy.
|
// TTLPolicy implements a ttl based ExpirationPolicy.
|
||||||
@@ -63,26 +63,29 @@ type TTLPolicy struct {
|
|||||||
|
|
||||||
// IsExpired returns true if the given object is older than the ttl, or it can't
|
// IsExpired returns true if the given object is older than the ttl, or it can't
|
||||||
// determine its age.
|
// determine its age.
|
||||||
func (p *TTLPolicy) IsExpired(obj *timestampedEntry) bool {
|
func (p *TTLPolicy) IsExpired(obj *TimestampedEntry) bool {
|
||||||
return p.Ttl > 0 && p.Clock.Since(obj.timestamp) > p.Ttl
|
return p.Ttl > 0 && p.Clock.Since(obj.Timestamp) > p.Ttl
|
||||||
}
|
}
|
||||||
|
|
||||||
// timestampedEntry is the only type allowed in a ExpirationCache.
|
// TimestampedEntry is the only type allowed in a ExpirationCache.
|
||||||
type timestampedEntry struct {
|
// Keep in mind that it is not safe to share timestamps between computers.
|
||||||
obj interface{}
|
// Behavior may be inconsistent if you get a timestamp from the API Server and
|
||||||
timestamp time.Time
|
// use it on the client machine as part of your ExpirationCache.
|
||||||
|
type TimestampedEntry struct {
|
||||||
|
Obj interface{}
|
||||||
|
Timestamp time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTimestampedEntry returns the timestampedEntry stored under the given key.
|
// getTimestampedEntry returns the TimestampedEntry stored under the given key.
|
||||||
func (c *ExpirationCache) getTimestampedEntry(key string) (*timestampedEntry, bool) {
|
func (c *ExpirationCache) getTimestampedEntry(key string) (*TimestampedEntry, bool) {
|
||||||
item, _ := c.cacheStorage.Get(key)
|
item, _ := c.cacheStorage.Get(key)
|
||||||
if tsEntry, ok := item.(*timestampedEntry); ok {
|
if tsEntry, ok := item.(*TimestampedEntry); ok {
|
||||||
return tsEntry, true
|
return tsEntry, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrExpire retrieves the object from the timestampedEntry if and only if it hasn't
|
// getOrExpire retrieves the object from the TimestampedEntry if and only if it hasn't
|
||||||
// already expired. It holds a write lock across deletion.
|
// already expired. It holds a write lock across deletion.
|
||||||
func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
|
func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
|
||||||
// Prevent all inserts from the time we deem an item as "expired" to when we
|
// Prevent all inserts from the time we deem an item as "expired" to when we
|
||||||
@@ -95,11 +98,11 @@ func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
if c.expirationPolicy.IsExpired(timestampedItem) {
|
if c.expirationPolicy.IsExpired(timestampedItem) {
|
||||||
klog.V(4).Infof("Entry %v: %+v has expired", key, timestampedItem.obj)
|
klog.V(4).Infof("Entry %v: %+v has expired", key, timestampedItem.Obj)
|
||||||
c.cacheStorage.Delete(key)
|
c.cacheStorage.Delete(key)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
return timestampedItem.obj, true
|
return timestampedItem.Obj, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByKey returns the item stored under the key, or sets exists=false.
|
// GetByKey returns the item stored under the key, or sets exists=false.
|
||||||
@@ -126,7 +129,7 @@ func (c *ExpirationCache) List() []interface{} {
|
|||||||
|
|
||||||
list := make([]interface{}, 0, len(items))
|
list := make([]interface{}, 0, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
obj := item.(*timestampedEntry).obj
|
obj := item.(*TimestampedEntry).Obj
|
||||||
if key, err := c.keyFunc(obj); err != nil {
|
if key, err := c.keyFunc(obj); err != nil {
|
||||||
list = append(list, obj)
|
list = append(list, obj)
|
||||||
} else if obj, exists := c.getOrExpire(key); exists {
|
} else if obj, exists := c.getOrExpire(key); exists {
|
||||||
@@ -151,7 +154,7 @@ func (c *ExpirationCache) Add(obj interface{}) error {
|
|||||||
c.expirationLock.Lock()
|
c.expirationLock.Lock()
|
||||||
defer c.expirationLock.Unlock()
|
defer c.expirationLock.Unlock()
|
||||||
|
|
||||||
c.cacheStorage.Add(key, ×tampedEntry{obj, c.clock.Now()})
|
c.cacheStorage.Add(key, &TimestampedEntry{obj, c.clock.Now()})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +187,7 @@ func (c *ExpirationCache) Replace(list []interface{}, resourceVersion string) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return KeyError{item, err}
|
return KeyError{item, err}
|
||||||
}
|
}
|
||||||
items[key] = ×tampedEntry{item, ts}
|
items[key] = &TimestampedEntry{item, ts}
|
||||||
}
|
}
|
||||||
c.expirationLock.Lock()
|
c.expirationLock.Lock()
|
||||||
defer c.expirationLock.Unlock()
|
defer c.expirationLock.Unlock()
|
||||||
@@ -199,10 +202,15 @@ func (c *ExpirationCache) Resync() error {
|
|||||||
|
|
||||||
// NewTTLStore creates and returns a ExpirationCache with a TTLPolicy
|
// NewTTLStore creates and returns a ExpirationCache with a TTLPolicy
|
||||||
func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store {
|
func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store {
|
||||||
|
return NewExpirationStore(keyFunc, &TTLPolicy{ttl, clock.RealClock{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExpirationStore creates and returns a ExpirationCache for a given policy
|
||||||
|
func NewExpirationStore(keyFunc KeyFunc, expirationPolicy ExpirationPolicy) Store {
|
||||||
return &ExpirationCache{
|
return &ExpirationCache{
|
||||||
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
|
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
|
||||||
keyFunc: keyFunc,
|
keyFunc: keyFunc,
|
||||||
clock: clock.RealClock{},
|
clock: clock.RealClock{},
|
||||||
expirationPolicy: &TTLPolicy{ttl, clock.RealClock{}},
|
expirationPolicy: expirationPolicy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ type FakeExpirationPolicy struct {
|
|||||||
RetrieveKeyFunc KeyFunc
|
RetrieveKeyFunc KeyFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FakeExpirationPolicy) IsExpired(obj *timestampedEntry) bool {
|
func (p *FakeExpirationPolicy) IsExpired(obj *TimestampedEntry) bool {
|
||||||
key, _ := p.RetrieveKeyFunc(obj)
|
key, _ := p.RetrieveKeyFunc(obj)
|
||||||
return !p.NeverExpire.Has(key)
|
return !p.NeverExpire.Has(key)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -228,6 +228,7 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
|||||||
// blindly overwrite existing values based on precedence
|
// blindly overwrite existing values based on precedence
|
||||||
if len(configAuthInfo.Token) > 0 {
|
if len(configAuthInfo.Token) > 0 {
|
||||||
mergedConfig.BearerToken = configAuthInfo.Token
|
mergedConfig.BearerToken = configAuthInfo.Token
|
||||||
|
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
||||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||||
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -499,8 +500,9 @@ func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error)
|
|||||||
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
|
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
|
||||||
icc.Host = server
|
icc.Host = server
|
||||||
}
|
}
|
||||||
if token := config.overrides.AuthInfo.Token; len(token) > 0 {
|
if len(config.overrides.AuthInfo.Token) > 0 || len(config.overrides.AuthInfo.TokenFile) > 0 {
|
||||||
icc.BearerToken = token
|
icc.BearerToken = config.overrides.AuthInfo.Token
|
||||||
|
icc.BearerTokenFile = config.overrides.AuthInfo.TokenFile
|
||||||
}
|
}
|
||||||
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
|
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
|
||||||
icc.TLSClientConfig.CAFile = certificateAuthorityFile
|
icc.TLSClientConfig.CAFile = certificateAuthorityFile
|
||||||
|
|||||||
+18
-35
@@ -19,8 +19,6 @@ package reference
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
@@ -30,8 +28,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Errors that could be returned by GetReference.
|
// Errors that could be returned by GetReference.
|
||||||
ErrNilObject = errors.New("can't reference a nil object")
|
ErrNilObject = errors.New("can't reference a nil object")
|
||||||
ErrNoSelfLink = errors.New("selfLink was empty, can't make reference")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetReference returns an ObjectReference which refers to the given
|
// GetReference returns an ObjectReference which refers to the given
|
||||||
@@ -47,20 +44,6 @@ func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*v1.ObjectReferen
|
|||||||
return ref, nil
|
return ref, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
|
||||||
|
|
||||||
// if the object referenced is actually persisted, we can just get kind from meta
|
|
||||||
// if we are building an object reference to something not yet persisted, we should fallback to scheme
|
|
||||||
kind := gvk.Kind
|
|
||||||
if len(kind) == 0 {
|
|
||||||
// TODO: this is wrong
|
|
||||||
gvks, _, err := scheme.ObjectKinds(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
kind = gvks[0].Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
// An object that implements only List has enough metadata to build a reference
|
// An object that implements only List has enough metadata to build a reference
|
||||||
var listMeta metav1.Common
|
var listMeta metav1.Common
|
||||||
objectMeta, err := meta.Accessor(obj)
|
objectMeta, err := meta.Accessor(obj)
|
||||||
@@ -73,29 +56,29 @@ func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*v1.ObjectReferen
|
|||||||
listMeta = objectMeta
|
listMeta = objectMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the object referenced is actually persisted, we can also get version from meta
|
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||||
version := gvk.GroupVersion().String()
|
|
||||||
if len(version) == 0 {
|
// If object meta doesn't contain data about kind and/or version,
|
||||||
selfLink := listMeta.GetSelfLink()
|
// we are falling back to scheme.
|
||||||
if len(selfLink) == 0 {
|
//
|
||||||
return nil, ErrNoSelfLink
|
// TODO: This doesn't work for CRDs, which are not registered in scheme.
|
||||||
}
|
if gvk.Empty() {
|
||||||
selfLinkUrl, err := url.Parse(selfLink)
|
gvks, _, err := scheme.ObjectKinds(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// example paths: /<prefix>/<version>/*
|
if len(gvks) == 0 || gvks[0].Empty() {
|
||||||
parts := strings.Split(selfLinkUrl.Path, "/")
|
return nil, fmt.Errorf("unexpected gvks registered for object %T: %v", obj, gvks)
|
||||||
if len(parts) < 4 {
|
|
||||||
return nil, fmt.Errorf("unexpected self link format: '%v'; got version '%v'", selfLink, version)
|
|
||||||
}
|
|
||||||
if parts[1] == "api" {
|
|
||||||
version = parts[2]
|
|
||||||
} else {
|
|
||||||
version = parts[2] + "/" + parts[3]
|
|
||||||
}
|
}
|
||||||
|
// TODO: The same object can be registered for multiple group versions
|
||||||
|
// (although in practise this doesn't seem to be used).
|
||||||
|
// In such case, the version set may not be correct.
|
||||||
|
gvk = gvks[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kind := gvk.Kind
|
||||||
|
version := gvk.GroupVersion().String()
|
||||||
|
|
||||||
// only has list metadata
|
// only has list metadata
|
||||||
if objectMeta == nil {
|
if objectMeta == nil {
|
||||||
return &v1.ObjectReference{
|
return &v1.ObjectReference{
|
||||||
|
|||||||
Reference in New Issue
Block a user