diff --git a/go.mod b/go.mod index 54b13037a..19acd533e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/netobserv/network-observability-cli -go 1.25.0 +go 1.25.7 require ( github.com/gdamore/tcell/v2 v2.13.7 @@ -16,7 +16,7 @@ require ( github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 - sigs.k8s.io/e2e-framework v0.6.0 + sigs.k8s.io/e2e-framework v0.7.0 ) require ( @@ -90,13 +90,13 @@ require ( k8s.io/api v0.35.3 k8s.io/apimachinery v0.35.3 k8s.io/client-go v0.35.3 - k8s.io/component-base v0.32.3 // indirect - k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/component-base v0.35.3 // indirect + k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect - sigs.k8s.io/controller-runtime v0.20.4 // indirect + sigs.k8s.io/controller-runtime v0.23.3 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 45847d177..344e57ff1 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,6 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -323,29 +321,29 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= -k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= -k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= +k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= +k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= -k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= -k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/component-base v0.35.3 h1:mbKbzoIMy7JDWS/wqZobYW1JDVRn/RKRaoMQHP9c4P0= +k8s.io/component-base v0.35.3/go.mod h1:IZ8LEG30kPN4Et5NeC7vjNv5aU73ku5MS15iZyvyMYk= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= -sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= -sigs.k8s.io/e2e-framework v0.6.0 h1:p7hFzHnLKO7eNsWGI2AbC1Mo2IYxidg49BiT4njxkrM= -sigs.k8s.io/e2e-framework v0.6.0/go.mod h1:IREnCHnKgRCioLRmNi0hxSJ1kJ+aAdjEKK/gokcZu4k= +sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= +sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/e2e-framework v0.7.0 h1:AHkySTC6MvnnMbVSxaO4z1m2MhQKNFP+2Ihs5pRNLlM= +sigs.k8s.io/e2e-framework v0.7.0/go.mod h1:1ZgXkUSjmnf18/JgHZNEATWjv48O5lJm9aI1QIsRdbw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/vendor/k8s.io/component-base/cli/flag/ciphersuites_flag.go b/vendor/k8s.io/component-base/cli/flag/ciphersuites_flag.go deleted file mode 100644 index 11adc2683..000000000 --- a/vendor/k8s.io/component-base/cli/flag/ciphersuites_flag.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flag - -import ( - "crypto/tls" - "fmt" - - "k8s.io/apimachinery/pkg/util/sets" -) - -var ( - // ciphers maps strings into tls package cipher constants in - // https://golang.org/pkg/crypto/tls/#pkg-constants - ciphers = map[string]uint16{} - insecureCiphers = map[string]uint16{} -) - -func init() { - for _, suite := range tls.CipherSuites() { - ciphers[suite.Name] = suite.ID - } - // keep legacy names for backward compatibility - ciphers["TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"] = tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - ciphers["TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"] = tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - - for _, suite := range tls.InsecureCipherSuites() { - insecureCiphers[suite.Name] = suite.ID - } -} - -// InsecureTLSCiphers returns the cipher suites implemented by crypto/tls which have -// security issues. -func InsecureTLSCiphers() map[string]uint16 { - cipherKeys := make(map[string]uint16, len(insecureCiphers)) - for k, v := range insecureCiphers { - cipherKeys[k] = v - } - return cipherKeys -} - -// InsecureTLSCipherNames returns a list of cipher suite names implemented by crypto/tls -// which have security issues. -func InsecureTLSCipherNames() []string { - cipherKeys := sets.NewString() - for key := range insecureCiphers { - cipherKeys.Insert(key) - } - return cipherKeys.List() -} - -// PreferredTLSCipherNames returns a list of cipher suite names implemented by crypto/tls. -func PreferredTLSCipherNames() []string { - cipherKeys := sets.NewString() - for key := range ciphers { - cipherKeys.Insert(key) - } - return cipherKeys.List() -} - -func allCiphers() map[string]uint16 { - acceptedCiphers := make(map[string]uint16, len(ciphers)+len(insecureCiphers)) - for k, v := range ciphers { - acceptedCiphers[k] = v - } - for k, v := range insecureCiphers { - acceptedCiphers[k] = v - } - return acceptedCiphers -} - -// TLSCipherPossibleValues returns all acceptable cipher suite names. -// This is a combination of both InsecureTLSCipherNames() and PreferredTLSCipherNames(). -func TLSCipherPossibleValues() []string { - cipherKeys := sets.NewString() - acceptedCiphers := allCiphers() - for key := range acceptedCiphers { - cipherKeys.Insert(key) - } - return cipherKeys.List() -} - -// TLSCipherSuites returns a list of cipher suite IDs from the cipher suite names passed. -func TLSCipherSuites(cipherNames []string) ([]uint16, error) { - if len(cipherNames) == 0 { - return nil, nil - } - ciphersIntSlice := make([]uint16, 0) - possibleCiphers := allCiphers() - for _, cipher := range cipherNames { - intValue, ok := possibleCiphers[cipher] - if !ok { - return nil, fmt.Errorf("Cipher suite %s not supported or doesn't exist", cipher) - } - ciphersIntSlice = append(ciphersIntSlice, intValue) - } - return ciphersIntSlice, nil -} - -var versions = map[string]uint16{ - "VersionTLS10": tls.VersionTLS10, - "VersionTLS11": tls.VersionTLS11, - "VersionTLS12": tls.VersionTLS12, - "VersionTLS13": tls.VersionTLS13, -} - -// TLSPossibleVersions returns all acceptable values for TLS Version. -func TLSPossibleVersions() []string { - versionsKeys := sets.NewString() - for key := range versions { - versionsKeys.Insert(key) - } - return versionsKeys.List() -} - -// TLSVersion returns the TLS Version ID for the version name passed. -func TLSVersion(versionName string) (uint16, error) { - if len(versionName) == 0 { - return DefaultTLSVersion(), nil - } - if version, ok := versions[versionName]; ok { - return version, nil - } - return 0, fmt.Errorf("unknown tls version %q", versionName) -} - -// DefaultTLSVersion defines the default TLS Version. -func DefaultTLSVersion() uint16 { - // Can't use SSLv3 because of POODLE and BEAST - // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher - // Can't use TLSv1.1 because of RC4 cipher usage - return tls.VersionTLS12 -} diff --git a/vendor/k8s.io/component-base/cli/flag/colon_separated_multimap_string_string.go b/vendor/k8s.io/component-base/cli/flag/colon_separated_multimap_string_string.go deleted file mode 100644 index 728fa520b..000000000 --- a/vendor/k8s.io/component-base/cli/flag/colon_separated_multimap_string_string.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flag - -import ( - "fmt" - "sort" - "strings" -) - -// ColonSeparatedMultimapStringString supports setting a map[string][]string from an encoding -// that separates keys from values with ':' and separates key-value pairs with ','. -// A key can be repeated multiple times, in which case the values are appended to a -// slice of strings associated with that key. Items in the list associated with a given -// key will appear in the order provided. -// For example: `a:hello,b:again,c:world,b:beautiful` results in `{"a": ["hello"], "b": ["again", "beautiful"], "c": ["world"]}` -// The first call to Set will clear the map before adding entries; subsequent calls will simply append to the map. -// This makes it possible to override default values with a command-line option rather than appending to defaults, -// while still allowing the distribution of key-value pairs across multiple flag invocations. -// For example: `--flag "a:hello" --flag "b:again" --flag "b:beautiful" --flag "c:world"` results in `{"a": ["hello"], "b": ["again", "beautiful"], "c": ["world"]}` -type ColonSeparatedMultimapStringString struct { - Multimap *map[string][]string - initialized bool // set to true after the first Set call - allowDefaultEmptyKey bool -} - -// NewColonSeparatedMultimapStringString takes a pointer to a map[string][]string and returns the -// ColonSeparatedMultimapStringString flag parsing shim for that map. -func NewColonSeparatedMultimapStringString(m *map[string][]string) *ColonSeparatedMultimapStringString { - return &ColonSeparatedMultimapStringString{Multimap: m} -} - -// NewColonSeparatedMultimapStringStringAllowDefaultEmptyKey takes a pointer to a map[string][]string and returns the -// ColonSeparatedMultimapStringString flag parsing shim for that map. It allows default empty key with no colon in the flag. -func NewColonSeparatedMultimapStringStringAllowDefaultEmptyKey(m *map[string][]string) *ColonSeparatedMultimapStringString { - return &ColonSeparatedMultimapStringString{Multimap: m, allowDefaultEmptyKey: true} -} - -// Set implements github.com/spf13/pflag.Value -func (m *ColonSeparatedMultimapStringString) Set(value string) error { - if m.Multimap == nil { - return fmt.Errorf("no target (nil pointer to map[string][]string)") - } - if !m.initialized || *m.Multimap == nil { - // clear default values, or allocate if no existing map - *m.Multimap = make(map[string][]string) - m.initialized = true - } - for _, pair := range strings.Split(value, ",") { - if len(pair) == 0 { - continue - } - kv := strings.SplitN(pair, ":", 2) - var k, v string - if m.allowDefaultEmptyKey && len(kv) == 1 { - v = strings.TrimSpace(kv[0]) - } else { - if len(kv) != 2 { - return fmt.Errorf("malformed pair, expect string:string") - } - k = strings.TrimSpace(kv[0]) - v = strings.TrimSpace(kv[1]) - } - (*m.Multimap)[k] = append((*m.Multimap)[k], v) - } - return nil -} - -// String implements github.com/spf13/pflag.Value -func (m *ColonSeparatedMultimapStringString) String() string { - type kv struct { - k string - v string - } - kvs := make([]kv, 0, len(*m.Multimap)) - for k, vs := range *m.Multimap { - for i := range vs { - kvs = append(kvs, kv{k: k, v: vs[i]}) - } - } - // stable sort by keys, order of values should be preserved - sort.SliceStable(kvs, func(i, j int) bool { - return kvs[i].k < kvs[j].k - }) - pairs := make([]string, 0, len(kvs)) - for i := range kvs { - pairs = append(pairs, fmt.Sprintf("%s:%s", kvs[i].k, kvs[i].v)) - } - return strings.Join(pairs, ",") -} - -// Type implements github.com/spf13/pflag.Value -func (m *ColonSeparatedMultimapStringString) Type() string { - return "colonSeparatedMultimapStringString" -} - -// Empty implements OmitEmpty -func (m *ColonSeparatedMultimapStringString) Empty() bool { - return len(*m.Multimap) == 0 -} diff --git a/vendor/k8s.io/component-base/cli/flag/configuration_map.go b/vendor/k8s.io/component-base/cli/flag/configuration_map.go deleted file mode 100644 index 911b05ec6..000000000 --- a/vendor/k8s.io/component-base/cli/flag/configuration_map.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2014 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 flag - -import ( - "fmt" - "sort" - "strings" -) - -type ConfigurationMap map[string]string - -func (m *ConfigurationMap) String() string { - pairs := []string{} - for k, v := range *m { - pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) - } - sort.Strings(pairs) - return strings.Join(pairs, ",") -} - -func (m *ConfigurationMap) Set(value string) error { - for _, s := range strings.Split(value, ",") { - if len(s) == 0 { - continue - } - arr := strings.SplitN(s, "=", 2) - if len(arr) == 2 { - (*m)[strings.TrimSpace(arr[0])] = strings.TrimSpace(arr[1]) - } else { - (*m)[strings.TrimSpace(arr[0])] = "" - } - } - return nil -} - -func (*ConfigurationMap) Type() string { - return "mapStringString" -} diff --git a/vendor/k8s.io/component-base/cli/flag/flags.go b/vendor/k8s.io/component-base/cli/flag/flags.go deleted file mode 100644 index 8d4a59ce9..000000000 --- a/vendor/k8s.io/component-base/cli/flag/flags.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright 2014 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 flag - -import ( - goflag "flag" - "strings" - - "github.com/spf13/pflag" - "k8s.io/klog/v2" -) - -var underscoreWarnings = make(map[string]struct{}) - -// WordSepNormalizeFunc changes all flags that contain "_" separators -func WordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { - if strings.Contains(name, "_") { - return pflag.NormalizedName(strings.Replace(name, "_", "-", -1)) - } - return pflag.NormalizedName(name) -} - -// WarnWordSepNormalizeFunc changes and warns for flags that contain "_" separators -func WarnWordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { - if strings.Contains(name, "_") { - nname := strings.Replace(name, "_", "-", -1) - if _, alreadyWarned := underscoreWarnings[name]; !alreadyWarned { - klog.Warningf("using an underscore in a flag name is not supported. %s has been converted to %s.", name, nname) - underscoreWarnings[name] = struct{}{} - } - - return pflag.NormalizedName(nname) - } - return pflag.NormalizedName(name) -} - -// InitFlags normalizes, parses, then logs the command line flags -func InitFlags() { - pflag.CommandLine.SetNormalizeFunc(WordSepNormalizeFunc) - pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) - pflag.Parse() - pflag.VisitAll(func(flag *pflag.Flag) { - klog.V(2).Infof("FLAG: --%s=%q", flag.Name, flag.Value) - }) -} - -// PrintFlags logs the flags in the flagset -func PrintFlags(flags *pflag.FlagSet) { - flags.VisitAll(func(flag *pflag.Flag) { - klog.V(1).Infof("FLAG: --%s=%q", flag.Name, flag.Value) - }) -} diff --git a/vendor/k8s.io/component-base/cli/flag/langle_separated_map_string_string.go b/vendor/k8s.io/component-base/cli/flag/langle_separated_map_string_string.go deleted file mode 100644 index bf8dbfb9b..000000000 --- a/vendor/k8s.io/component-base/cli/flag/langle_separated_map_string_string.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flag - -import ( - "fmt" - "sort" - "strings" -) - -// LangleSeparatedMapStringString can be set from the command line with the format `--flag "string 0 { - s = s + ":" + strings.Join(nkc.Names, ",") - } - return s -} - -func (nkc *NamedCertKey) Set(value string) error { - cs := strings.SplitN(value, ":", 2) - var keycert string - if len(cs) == 2 { - var names string - keycert, names = strings.TrimSpace(cs[0]), strings.TrimSpace(cs[1]) - if names == "" { - return errors.New("empty names list is not allowed") - } - nkc.Names = nil - for _, name := range strings.Split(names, ",") { - nkc.Names = append(nkc.Names, strings.TrimSpace(name)) - } - } else { - nkc.Names = nil - keycert = strings.TrimSpace(cs[0]) - } - cs = strings.Split(keycert, ",") - if len(cs) != 2 { - return errors.New("expected comma separated certificate and key file paths") - } - nkc.CertFile = strings.TrimSpace(cs[0]) - nkc.KeyFile = strings.TrimSpace(cs[1]) - return nil -} - -func (*NamedCertKey) Type() string { - return "namedCertKey" -} - -// NamedCertKeyArray is a flag value parsing NamedCertKeys, each passed with its own -// flag instance (in contrast to comma separated slices). -type NamedCertKeyArray struct { - value *[]NamedCertKey - changed bool -} - -var _ flag.Value = &NamedCertKeyArray{} - -// NewNamedKeyCertArray creates a new NamedCertKeyArray with the internal value -// pointing to p. -func NewNamedCertKeyArray(p *[]NamedCertKey) *NamedCertKeyArray { - return &NamedCertKeyArray{ - value: p, - } -} - -func (a *NamedCertKeyArray) Set(val string) error { - nkc := NamedCertKey{} - err := nkc.Set(val) - if err != nil { - return err - } - if !a.changed { - *a.value = []NamedCertKey{nkc} - a.changed = true - } else { - *a.value = append(*a.value, nkc) - } - return nil -} - -func (a *NamedCertKeyArray) Type() string { - return "namedCertKey" -} - -func (a *NamedCertKeyArray) String() string { - nkcs := make([]string, 0, len(*a.value)) - for i := range *a.value { - nkcs = append(nkcs, (*a.value)[i].String()) - } - return "[" + strings.Join(nkcs, ";") + "]" -} diff --git a/vendor/k8s.io/component-base/cli/flag/noop.go b/vendor/k8s.io/component-base/cli/flag/noop.go deleted file mode 100644 index 03f7f14c0..000000000 --- a/vendor/k8s.io/component-base/cli/flag/noop.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flag - -import ( - goflag "flag" - "github.com/spf13/pflag" -) - -// NoOp implements goflag.Value and plfag.Value, -// but has a noop Set implementation -type NoOp struct{} - -var _ goflag.Value = NoOp{} -var _ pflag.Value = NoOp{} - -func (NoOp) String() string { - return "" -} - -func (NoOp) Set(val string) error { - return nil -} - -func (NoOp) Type() string { - return "NoOp" -} diff --git a/vendor/k8s.io/component-base/cli/flag/omitempty.go b/vendor/k8s.io/component-base/cli/flag/omitempty.go deleted file mode 100644 index c354754ea..000000000 --- a/vendor/k8s.io/component-base/cli/flag/omitempty.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flag - -// OmitEmpty is an interface for flags to report whether their underlying value -// is "empty." If a flag implements OmitEmpty and returns true for a call to Empty(), -// it is assumed that flag may be omitted from the command line. -type OmitEmpty interface { - Empty() bool -} diff --git a/vendor/k8s.io/component-base/cli/flag/sectioned.go b/vendor/k8s.io/component-base/cli/flag/sectioned.go deleted file mode 100644 index 235742876..000000000 --- a/vendor/k8s.io/component-base/cli/flag/sectioned.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flag - -import ( - "bytes" - "fmt" - "io" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -const ( - usageFmt = "Usage:\n %s\n" -) - -// NamedFlagSets stores named flag sets in the order of calling FlagSet. -type NamedFlagSets struct { - // Order is an ordered list of flag set names. - Order []string - // FlagSets stores the flag sets by name. - FlagSets map[string]*pflag.FlagSet - // NormalizeNameFunc is the normalize function which used to initialize FlagSets created by NamedFlagSets. - NormalizeNameFunc func(f *pflag.FlagSet, name string) pflag.NormalizedName -} - -// FlagSet returns the flag set with the given name and adds it to the -// ordered name list if it is not in there yet. -func (nfs *NamedFlagSets) FlagSet(name string) *pflag.FlagSet { - if nfs.FlagSets == nil { - nfs.FlagSets = map[string]*pflag.FlagSet{} - } - if _, ok := nfs.FlagSets[name]; !ok { - flagSet := pflag.NewFlagSet(name, pflag.ExitOnError) - flagSet.SetNormalizeFunc(pflag.CommandLine.GetNormalizeFunc()) - if nfs.NormalizeNameFunc != nil { - flagSet.SetNormalizeFunc(nfs.NormalizeNameFunc) - } - nfs.FlagSets[name] = flagSet - nfs.Order = append(nfs.Order, name) - } - return nfs.FlagSets[name] -} - -// PrintSections prints the given names flag sets in sections, with the maximal given column number. -// If cols is zero, lines are not wrapped. -func PrintSections(w io.Writer, fss NamedFlagSets, cols int) { - for _, name := range fss.Order { - fs := fss.FlagSets[name] - if !fs.HasFlags() { - continue - } - - wideFS := pflag.NewFlagSet("", pflag.ExitOnError) - wideFS.AddFlagSet(fs) - - var zzz string - if cols > 24 { - zzz = strings.Repeat("z", cols-24) - wideFS.Int(zzz, 0, strings.Repeat("z", cols-24)) - } - - var buf bytes.Buffer - fmt.Fprintf(&buf, "\n%s flags:\n\n%s", strings.ToUpper(name[:1])+name[1:], wideFS.FlagUsagesWrapped(cols)) - - if cols > 24 { - i := strings.Index(buf.String(), zzz) - lines := strings.Split(buf.String()[:i], "\n") - fmt.Fprint(w, strings.Join(lines[:len(lines)-1], "\n")) - fmt.Fprintln(w) - } else { - fmt.Fprint(w, buf.String()) - } - } -} - -// SetUsageAndHelpFunc set both usage and help function. -// Print the flag sets we need instead of all of them. -func SetUsageAndHelpFunc(cmd *cobra.Command, fss NamedFlagSets, cols int) { - cmd.SetUsageFunc(func(cmd *cobra.Command) error { - fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine()) - PrintSections(cmd.OutOrStderr(), fss, cols) - return nil - }) - cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine()) - PrintSections(cmd.OutOrStdout(), fss, cols) - }) -} diff --git a/vendor/k8s.io/component-base/cli/flag/string_flag.go b/vendor/k8s.io/component-base/cli/flag/string_flag.go deleted file mode 100644 index 331bdb66e..000000000 --- a/vendor/k8s.io/component-base/cli/flag/string_flag.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2014 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 flag - -// StringFlag is a string flag compatible with flags and pflags that keeps track of whether it had a value supplied or not. -type StringFlag struct { - // If Set has been invoked this value is true - provided bool - // The exact value provided on the flag - value string -} - -func NewStringFlag(defaultVal string) StringFlag { - return StringFlag{value: defaultVal} -} - -func (f *StringFlag) Default(value string) { - f.value = value -} - -func (f StringFlag) String() string { - return f.value -} - -func (f StringFlag) Value() string { - return f.value -} - -func (f *StringFlag) Set(value string) error { - f.value = value - f.provided = true - - return nil -} - -func (f StringFlag) Provided() bool { - return f.provided -} - -func (f *StringFlag) Type() string { - return "string" -} diff --git a/vendor/k8s.io/component-base/cli/flag/string_slice_flag.go b/vendor/k8s.io/component-base/cli/flag/string_slice_flag.go deleted file mode 100644 index ad0d07d75..000000000 --- a/vendor/k8s.io/component-base/cli/flag/string_slice_flag.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flag - -import ( - goflag "flag" - "fmt" - "strings" - - "github.com/spf13/pflag" -) - -// StringSlice implements goflag.Value and plfag.Value, -// and allows set to be invoked repeatedly to accumulate values. -type StringSlice struct { - value *[]string - changed bool -} - -func NewStringSlice(s *[]string) *StringSlice { - return &StringSlice{value: s} -} - -var _ goflag.Value = &StringSlice{} -var _ pflag.Value = &StringSlice{} - -func (s *StringSlice) String() string { - if s == nil || s.value == nil { - return "" - } - return strings.Join(*s.value, " ") -} - -func (s *StringSlice) Set(val string) error { - if s.value == nil { - return fmt.Errorf("no target (nil pointer to []string)") - } - if !s.changed { - *s.value = make([]string, 0) - } - *s.value = append(*s.value, val) - s.changed = true - return nil -} - -func (StringSlice) Type() string { - return "sliceString" -} diff --git a/vendor/k8s.io/component-base/cli/flag/tristate.go b/vendor/k8s.io/component-base/cli/flag/tristate.go deleted file mode 100644 index cf16376bf..000000000 --- a/vendor/k8s.io/component-base/cli/flag/tristate.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright 2014 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 flag - -import ( - "fmt" - "strconv" -) - -// Tristate is a flag compatible with flags and pflags that -// keeps track of whether it had a value supplied or not. -type Tristate int - -const ( - Unset Tristate = iota // 0 - True - False -) - -func (f *Tristate) Default(value bool) { - *f = triFromBool(value) -} - -func (f Tristate) String() string { - b := boolFromTri(f) - return fmt.Sprintf("%t", b) -} - -func (f Tristate) Value() bool { - b := boolFromTri(f) - return b -} - -func (f *Tristate) Set(value string) error { - boolVal, err := strconv.ParseBool(value) - if err != nil { - return err - } - - *f = triFromBool(boolVal) - return nil -} - -func (f Tristate) Provided() bool { - if f != Unset { - return true - } - return false -} - -func (f *Tristate) Type() string { - return "tristate" -} - -func boolFromTri(t Tristate) bool { - if t == True { - return true - } else { - return false - } -} - -func triFromBool(b bool) Tristate { - if b { - return True - } else { - return False - } -} diff --git a/vendor/k8s.io/component-base/featuregate/feature_gate.go b/vendor/k8s.io/component-base/featuregate/feature_gate.go index b6f08a6cd..7c18ef4aa 100644 --- a/vendor/k8s.io/component-base/featuregate/feature_gate.go +++ b/vendor/k8s.io/component-base/featuregate/feature_gate.go @@ -19,7 +19,9 @@ package featuregate import ( "context" "fmt" + "maps" "reflect" + "slices" "sort" "strconv" "strings" @@ -63,7 +65,7 @@ var ( } // Special handling for a few gates. - specialFeatures = map[Feature]func(known map[Feature]VersionedSpecs, enabled map[Feature]bool, val bool, cVer *version.Version){ + specialFeatures = map[Feature]func(known map[Feature]VersionedSpecs, enabled map[Feature]bool, val bool, emuVer, minCompatVer *version.Version){ allAlphaGate: setUnsetAlphaGates, allBetaGate: setUnsetBetaGates, } @@ -80,6 +82,39 @@ type FeatureSpec struct { // If multiple FeatureSpecs exist for a Feature, the one with the highest version that is less // than or equal to the effective version of the component is used. Version *version.Version + // MinCompatibilityVersion indicates the lowest version that this feature spec is compatible with. + // The component's minimum compatibility version must be greater than or equal to this version for this spec to be used. + // This allows features with skew compatibility implications to be introduced as Beta, + // but only on by default once the minimum compatibility version is high enough. + // If unspecified, it is inherited from the previous feature stage. + // If specified, it must be preceded by another FeatureSpec with the same version and different default but without the MinCompatibilityVersion. + // i.e. MinCompatibilityVersion should only be used to specify a different default value at the same version. + // + // Version vs. MinCompatibilityVersion: + // - Version determines the stability of the feature based on the server's emulation version. + // - MinCompatibilityVersion adds a further check based on the version compatibility + // with all servers in the control plane this server expects to communicate with. + // + // Example: + // FeatureA: []FeatureSpec{ + // {Version: version.MustParse("1.35"), Default: false, PreRelease: Beta}, + // {Version: version.MustParse("1.35"), Default: true, PreRelease: Beta, MinCompatibilityVersion: version.MustParse("1.35")}, + // } + // In a server running version 1.35: + // - With --min-compatibility-version=1.34 (default), FeatureA is Beta and disabled by default. + // - With --min-compatibility-version=1.35, FeatureA becomes enabled by default. + MinCompatibilityVersion *version.Version +} + +func (spec *FeatureSpec) HelpString(featureName Feature, includeCompatVer bool) string { + if spec.PreRelease == GA || spec.PreRelease == Deprecated || spec.PreRelease == PreAlpha { + return "" + } + s := fmt.Sprintf("%s=true|false (%s - default=%t)", featureName, spec.PreRelease, spec.Default) + if includeCompatVer && spec.MinCompatibilityVersion != nil { + return s + fmt.Sprintf(" if --min-compatibility-version>=%s", spec.MinCompatibilityVersion) + } + return s } type VersionedSpecs []FeatureSpec @@ -111,6 +146,8 @@ type FeatureGate interface { Enabled(key Feature) bool // KnownFeatures returns a slice of strings describing the FeatureGate's known features. KnownFeatures() []string + // Dependencies returns a copy of the known feature dependencies. + Dependencies() map[Feature][]Feature // DeepCopy returns a deep copy of the FeatureGate object, such that gates can be // set on the copy without mutating the original. This is useful for validating // config against potential feature gate changes before committing those changes. @@ -161,14 +198,26 @@ type MutableVersionedFeatureGate interface { // If set, the feature gate would enable/disable features based on // feature availability and pre-release at the emulated version instead of the binary version. EmulationVersion() *version.Version - // SetEmulationVersion overrides the emulationVersion of the feature gate. + // MinCompatibilityVersion returns the minimum version of all control plane components + // that the current server must maintain compatibility with. + // This is used to gate features that have cross-component compatibility concerns, for example, during cluster upgrades/rollbacks. + // If not explicitly set, it defaults to one minor version prior to EmulationVersion(). + // A feature stage is disabled if its FeatureSpec.MinCompatibilityVersion is greater than this version. + MinCompatibilityVersion() *version.Version + // SetEmulationVersion overrides the emulationVersion of the feature gate, and + // overrides the minCompatibilityVersion to 1 minor before emulationVersion.` // Otherwise, the emulationVersion will be the same as the binary version. // If set, the feature defaults and availability will be as if the binary is at the emulated version. SetEmulationVersion(emulationVersion *version.Version) error + // SetEmulationVersion overrides the emulationVersion and minCompatibilityVersion of the feature gate. + SetEmulationVersionAndMinCompatibilityVersion(emulationVersion *version.Version, minCompatibilityVersion *version.Version) error // GetAll returns a copy of the map of known feature names to versioned feature specs. GetAllVersioned() map[Feature]VersionedSpecs // AddVersioned adds versioned feature specs to the featureGate. AddVersioned(features map[Feature]VersionedSpecs) error + // AddDependencies marks features that depend on other features. Must be called after all + // referenced features have already been added (dependents & depnedencies). Cycles are forbidden. + AddDependencies(features map[Feature][]Feature) error // OverrideDefaultAtVersion sets a local override for the registered default value of a named // feature for the prerelease lifecycle the given version is at. // If the feature has not been previously registered (e.g. by a call to Add), @@ -196,14 +245,15 @@ type MutableVersionedFeatureGate interface { type featureGate struct { featureGateName string - special map[Feature]func(map[Feature]VersionedSpecs, map[Feature]bool, bool, *version.Version) + special map[Feature]func(map[Feature]VersionedSpecs, map[Feature]bool, bool, *version.Version, *version.Version) // lock guards writes to all below fields. lock sync.Mutex // known holds a map[Feature]FeatureSpec known atomic.Value // enabled holds a map[Feature]bool - enabled atomic.Value + enabled atomic.Value + dependencies atomic.Pointer[map[Feature][]Feature] // enabledRaw holds a raw map[string]bool of the parsed flag. // It keeps the original values of "special" features like "all alpha gates", // while enabled keeps the values of all resolved features. @@ -212,16 +262,17 @@ type featureGate struct { closed bool // queriedFeatures stores all the features that have been queried through the Enabled interface. // It is reset when SetEmulationVersion is called. - queriedFeatures atomic.Value - emulationVersion atomic.Pointer[version.Version] + queriedFeatures atomic.Value + emulationVersion atomic.Pointer[version.Version] + minCompatibilityVersion atomic.Pointer[version.Version] } -func setUnsetAlphaGates(known map[Feature]VersionedSpecs, enabled map[Feature]bool, val bool, cVer *version.Version) { +func setUnsetAlphaGates(known map[Feature]VersionedSpecs, enabled map[Feature]bool, val bool, emuVer, minCompatVer *version.Version) { for k, v := range known { if k == "AllAlpha" || k == "AllBeta" { continue } - featureSpec := featureSpecAtEmulationVersion(v, cVer) + featureSpec := featureSpecAtEmulationAndMinCompatVersion(v, emuVer, minCompatVer) if featureSpec.PreRelease == Alpha { if _, found := enabled[k]; !found { enabled[k] = val @@ -230,12 +281,12 @@ func setUnsetAlphaGates(known map[Feature]VersionedSpecs, enabled map[Feature]bo } } -func setUnsetBetaGates(known map[Feature]VersionedSpecs, enabled map[Feature]bool, val bool, cVer *version.Version) { +func setUnsetBetaGates(known map[Feature]VersionedSpecs, enabled map[Feature]bool, val bool, emuVer, minCompatVer *version.Version) { for k, v := range known { if k == "AllAlpha" || k == "AllBeta" { continue } - featureSpec := featureSpecAtEmulationVersion(v, cVer) + featureSpec := featureSpecAtEmulationAndMinCompatVersion(v, emuVer, minCompatVer) if featureSpec.PreRelease == Beta { if _, found := enabled[k]; !found { enabled[k] = val @@ -252,8 +303,13 @@ var _ pflag.Value = &featureGate{} var internalPackages = []string{"k8s.io/component-base/featuregate/feature_gate.go"} // NewVersionedFeatureGate creates a feature gate with the emulation version set to the provided version. +// Equivalent to calling NewVersionedFeatureGateWithMinCompatibility with emulationVersion, emulationVersion-1. // SetEmulationVersion can be called after to change emulation version to a desired value. func NewVersionedFeatureGate(emulationVersion *version.Version) *featureGate { + return NewVersionedFeatureGateWithMinCompatibility(emulationVersion, emulationVersion.SubtractMinor(1)) +} + +func NewVersionedFeatureGateWithMinCompatibility(emulationVersion, minCompatibilityVersion *version.Version) *featureGate { known := map[Feature]VersionedSpecs{} for k, v := range defaultFeatures { known[k] = v @@ -264,11 +320,12 @@ func NewVersionedFeatureGate(emulationVersion *version.Version) *featureGate { special: specialFeatures, } f.known.Store(known) + f.dependencies.Store(new(map[Feature][]Feature)) f.enabled.Store(map[Feature]bool{}) f.enabledRaw.Store(map[string]bool{}) f.emulationVersion.Store(emulationVersion) + f.minCompatibilityVersion.Store(minCompatibilityVersion) f.queriedFeatures.Store(sets.Set[Feature]{}) - klog.V(1).Infof("new feature gate with emulationVersion=%s", f.emulationVersion.Load().String()) return f } @@ -310,16 +367,15 @@ func (f *featureGate) Validate() []error { return []error{fmt.Errorf("cannot cast enabledRaw to map[string]bool")} } enabled := map[Feature]bool{} - return f.unsafeSetFromMap(enabled, m, f.EmulationVersion()) + return f.unsafeSetFromMap(enabled, m, f.EmulationVersion(), f.MinCompatibilityVersion()) } // unsafeSetFromMap stores flag gates for known features from a map[string]bool into an enabled map. -func (f *featureGate) unsafeSetFromMap(enabled map[Feature]bool, m map[string]bool, emulationVersion *version.Version) []error { +func (f *featureGate) unsafeSetFromMap(enabled map[Feature]bool, m map[string]bool, emulationVersion, minCompatibilityVersion *version.Version) []error { var errs []error // Copy existing state known := map[Feature]VersionedSpecs{} for k, v := range f.known.Load().(map[Feature]VersionedSpecs) { - sort.Sort(v) known[k] = v } @@ -331,19 +387,19 @@ func (f *featureGate) unsafeSetFromMap(enabled map[Feature]bool, m map[string]bo errs = append(errs, fmt.Errorf("unrecognized feature gate: %s", k)) return errs } - featureSpec := featureSpecAtEmulationVersion(versionedSpecs, emulationVersion) + featureSpec := featureSpecAtEmulationAndMinCompatVersion(versionedSpecs, emulationVersion, minCompatibilityVersion) if featureSpec.LockToDefault && featureSpec.Default != v { errs = append(errs, fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default)) continue } // Handle "special" features like "all alpha gates" if fn, found := f.special[key]; found { - fn(known, enabled, v, emulationVersion) + fn(known, enabled, v, emulationVersion, minCompatibilityVersion) enabled[key] = v continue } if featureSpec.PreRelease == PreAlpha { - errs = append(errs, fmt.Errorf("cannot set feature gate %v to %v, feature is PreAlpha at emulated version %s", k, v, emulationVersion.String())) + errs = append(errs, fmt.Errorf("cannot set feature gate %v to %v, feature is PreAlpha at emulated version %s, min compatibility version %s", k, v, emulationVersion, minCompatibilityVersion)) continue } enabled[key] = v @@ -354,6 +410,29 @@ func (f *featureGate) unsafeSetFromMap(enabled map[Feature]bool, m map[string]bo klog.Warningf("Setting GA feature gate %s=%t. It will be removed in a future release.", k, v) } } + + if len(errs) > 0 { + return errs + } + + // If enabled features were set successfully, validate them against the dependencies. + dependencies := *f.dependencies.Load() + for feature, deps := range dependencies { + if !featureEnabled(feature, enabled, known, emulationVersion, minCompatibilityVersion) { + continue + } + + var disabledDeps []Feature + for _, dep := range deps { + if !featureEnabled(dep, enabled, known, emulationVersion, minCompatibilityVersion) { + disabledDeps = append(disabledDeps, dep) + } + } + if len(disabledDeps) > 0 { + errs = append(errs, fmt.Errorf("%s is enabled, but depends on features that are disabled: %v", feature, disabledDeps)) + } + } + return errs } @@ -380,7 +459,7 @@ func (f *featureGate) SetFromMap(m map[string]bool) error { } f.enabledRaw.Store(enabledRaw) - errs := f.unsafeSetFromMap(enabled, enabledRaw, f.EmulationVersion()) + errs := f.unsafeSetFromMap(enabled, enabledRaw, f.EmulationVersion(), f.MinCompatibilityVersion()) if len(errs) == 0 { // Persist changes f.enabled.Store(enabled) @@ -422,33 +501,226 @@ func (f *featureGate) AddVersioned(features map[Feature]VersionedSpecs) error { if f.closed { return fmt.Errorf("cannot add a feature gate after adding it to the flag set") } - // Copy existing state known := f.GetAllVersioned() for name, specs := range features { - sort.Sort(specs) + var completedSpecs VersionedSpecs + // Validate new specs are well-formed + var lastSpec FeatureSpec + var wasBeta, wasGA, wasDeprecated, wasLockedToDefault bool + for i, spec := range specs { + if spec.Version == nil { + return fmt.Errorf("feature %q did not provide a version", name) + } + if len(spec.Version.Components()) != 2 { + return fmt.Errorf("feature %q specified patch version: %s", name, spec.Version) + } + if spec.MinCompatibilityVersion != nil { + if i == 0 || !spec.Version.EqualTo(lastSpec.Version) { + return fmt.Errorf("feature %q specified MinCompatibilityVersion: %s without a preceding entry at the same Version without MinCompatibilityVersion set", name, spec.MinCompatibilityVersion) + } + if len(spec.MinCompatibilityVersion.Components()) != 2 { + return fmt.Errorf("feature %q specified patch MinCompatibilityVersion: %s", name, spec.MinCompatibilityVersion) + } + if spec.MinCompatibilityVersion.GreaterThan(spec.Version) { + return fmt.Errorf("feature %q MinCompatibilityVersion %s greater than Version %s", name, spec.MinCompatibilityVersion, spec.Version) + } + } + // gates that begin as deprecated must indicate their prior state + if i == 0 && spec.PreRelease == Deprecated && spec.Version.Minor() != 0 { + return fmt.Errorf("feature %q introduced as deprecated must provide a 1.0 entry indicating initial state", name) + } + if i > 0 { + // either versions or minCompatibilityVersions have to increase + if spec.Version.LessThan(lastSpec.Version) { + return fmt.Errorf("feature %q lists version transitions in decreasing order (%s < %s)", name, spec.Version, lastSpec.Version) + } + if spec.MinCompatibilityVersion != nil && lastSpec.MinCompatibilityVersion != nil && spec.MinCompatibilityVersion.LessThan(lastSpec.MinCompatibilityVersion) { + return fmt.Errorf("feature %q lists min compatibility version transitions in decreasing order (%s < %s)", name, spec.MinCompatibilityVersion, lastSpec.MinCompatibilityVersion) + } + if spec.Version.EqualTo(lastSpec.Version) { + if spec.MinCompatibilityVersion.EqualTo(lastSpec.MinCompatibilityVersion) { + return fmt.Errorf("feature %q lists duplicate entries with the same versions (%s = %s)", name, spec.Version, lastSpec.Version) + } + if spec.PreRelease != lastSpec.PreRelease { + return fmt.Errorf("feature %q lists stability changes with the same version (%s -> %s)", name, lastSpec.PreRelease, spec.PreRelease) + } + if spec.LockToDefault != lastSpec.LockToDefault { + return fmt.Errorf("feature %q lists locked to default changes with the same version (%v -> %v)", name, lastSpec.LockToDefault, spec.LockToDefault) + } + } + if spec.PreRelease == lastSpec.PreRelease && spec.Default == lastSpec.Default && spec.LockToDefault == lastSpec.LockToDefault { + return fmt.Errorf("feature %q lists transition without stability change (%s -> %s)", name, lastSpec.Version, spec.Version) + } + if wasLockedToDefault && !spec.LockToDefault { + return fmt.Errorf("feature %q must not unlock after locking to default", name) + } + // stability must not regress from ga --> {beta,alpha} or beta --> alpha, and + // Deprecated state must be the terminal state + switch { + case spec.PreRelease != Deprecated && wasDeprecated: + return fmt.Errorf("deprecated feature %q must not resurrect from its terminal state", name) + case spec.PreRelease == Alpha && (wasBeta || wasGA): + return fmt.Errorf("feature %q regresses stability from more stable level to %s in %s", name, spec.PreRelease, spec.Version) + case spec.PreRelease == Beta && wasGA: + return fmt.Errorf("feature %q regresses stability from more stable level to %s in %s", name, spec.PreRelease, spec.Version) + } + } + completedSpec := spec + // set MinCompatibilityVersion to the last MinCompatibilityVersion if unspecified. + // this makes sure MinCompatibilityVersion does not decrease. + // For example: + // FeatureA: { + // {Version: version.MustParse("1.35"), Default: false, PreRelease: Beta}, + // {Version: version.MustParse("1.35"), Default: true, PreRelease: Beta, MinCompatibilityVersion: 1.35}, + // {Version: version.MustParse("1.37"), Default: true, PreRelease: GA}, + // }, + // FeatureA should have MinCompatibilityVersion at least 1.35 at GA. + if completedSpec.MinCompatibilityVersion == nil && i > 0 { + completedSpec.MinCompatibilityVersion = lastSpec.MinCompatibilityVersion + } + completedSpecs = append(completedSpecs, completedSpec) + lastSpec = completedSpec + wasBeta = wasBeta || spec.PreRelease == Beta + wasGA = wasGA || spec.PreRelease == GA + wasDeprecated = wasDeprecated || spec.PreRelease == Deprecated + wasLockedToDefault = wasLockedToDefault || spec.LockToDefault + } if existingSpec, found := known[name]; found { - sort.Sort(existingSpec) - if reflect.DeepEqual(existingSpec, specs) { + if reflect.DeepEqual(existingSpec, completedSpecs) { continue } return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec) } - known[name] = specs + known[name] = completedSpecs } - // Persist updated state f.known.Store(known) return nil } +// AddDependencies adds feature gate dependencies. +func (f *featureGate) AddDependencies(dependencies map[Feature][]Feature) error { + f.lock.Lock() + defer f.lock.Unlock() + + if f.closed { + return fmt.Errorf("cannot add a feature gate dependency after adding it to the flag set") + } + // Copy existing state + known := f.GetAllVersioned() + + // Merge existing dependencies in. + existing := *f.dependencies.Load() + for k, v := range existing { + dependencies[k] = append(dependencies[k], v...) + } + + // Sort and compact all dependency lists. + for k, v := range dependencies { + slices.Sort(v) + dependencies[k] = slices.Compact(v) + } + + // Validate dependencies for each emulated version: + // 1. Features & dependencies must be known + // 2. Features cannot depend on features with a lower prerelease level + // 3. Enabled features cannot depend on disabled features + // 4. Locked-to-default featurs cannot depend on unlocked features + for feature, deps := range dependencies { + versionedFeature, ok := known[feature] + if !ok { + return fmt.Errorf("cannot add dependency for unknown feature %s", feature) + } + + for _, dep := range deps { + versionedDep, ok := known[dep] + if !ok { + return fmt.Errorf("cannot add dependency from %s to unknown feature %s", feature, dep) + } + + // Check versions starting with the most recent for more intuitive error messages. + for _, spec := range slices.Backward(versionedFeature) { + // depSpec is the effective FeatureSpec for the dependency at the version declared by the dependent FeatureSpec. + depSpec := featureSpecAtEmulationAndMinCompatVersion(versionedDep, spec.Version, spec.MinCompatibilityVersion) + + if stabilityOrder(spec.PreRelease) > stabilityOrder(depSpec.PreRelease) { + return fmt.Errorf("%s feature %s cannot depend on %s feature %s at version %s", spec.PreRelease, feature, depSpec.PreRelease, dep, spec.Version) + } + if spec.Default && !depSpec.Default { + return fmt.Errorf("default-enabled feature %s cannot depend on default-disabled feature %s at version %s", feature, dep, spec.Version) + } + if spec.LockToDefault && !depSpec.LockToDefault { + return fmt.Errorf("locked-to-default feature %s cannot depend on unlocked feature %s at version %s", feature, dep, spec.Version) + } + } + } + } + + // Check for cycles + visited := map[Feature]bool{} + finished := map[Feature]bool{} + var detectCycles func(Feature) error + detectCycles = func(feature Feature) error { + if finished[feature] { + return nil + } + if visited[feature] { + return fmt.Errorf("cycle detected with feature %s", feature) + } + visited[feature] = true + for _, dep := range dependencies[feature] { + if err := detectCycles(dep); err != nil { + return err + } + } + finished[feature] = true + return nil + } + for feature := range dependencies { + if err := detectCycles(feature); err != nil { + return err + } + } + + // Persist updated state + f.dependencies.Store(&dependencies) + + return nil +} + +// stabilityOrder converts prereleases to a numerical value for dependency comparison. +// Features cannot depend on features with a lower prerelease level. +func stabilityOrder(p prerelease) int { + switch p { + case Deprecated: + return 0 // Non-deprecated features cannot depend on deprecated features. + case PreAlpha, Alpha: // Alpha features are allowed to depend on pre-alpha features. + return 1 + case Beta: + return 2 + case GA: + return 3 + default: + return -1 // Unknown prerelease + } +} + +func (f *featureGate) Dependencies() map[Feature][]Feature { + return maps.Clone(*f.dependencies.Load()) +} + func (f *featureGate) OverrideDefault(name Feature, override bool) error { - return f.OverrideDefaultAtVersion(name, override, f.EmulationVersion()) + return f.overrideDefaultAtEmulationAndMinCompatVersion(name, override, f.EmulationVersion(), f.MinCompatibilityVersion()) } func (f *featureGate) OverrideDefaultAtVersion(name Feature, override bool, ver *version.Version) error { + return f.overrideDefaultAtEmulationAndMinCompatVersion(name, override, ver, nil) +} + +func (f *featureGate) overrideDefaultAtEmulationAndMinCompatVersion(name Feature, override bool, emuVer, minCompatVer *version.Version) error { f.lock.Lock() defer f.lock.Unlock() @@ -463,12 +735,12 @@ func (f *featureGate) OverrideDefaultAtVersion(name Feature, override bool, ver if !ok { return fmt.Errorf("cannot override default: feature %q is not registered", name) } - spec := featureSpecAtEmulationVersion(specs, ver) + spec := featureSpecAtEmulationAndMinCompatVersion(specs, emuVer, minCompatVer) switch { case spec.LockToDefault: return fmt.Errorf("cannot override default: feature %q default is locked to %t", name, spec.Default) case spec.PreRelease == PreAlpha: - return fmt.Errorf("cannot override default: feature %q is not available before version %s", name, ver.String()) + return fmt.Errorf("cannot override default: feature %q is not available before version %s", name, emuVer) case spec.PreRelease == Deprecated: klog.Warningf("Overriding default of deprecated feature gate %s=%t. It will be removed in a future release.", name, override) case spec.PreRelease == GA: @@ -488,9 +760,10 @@ func (f *featureGate) GetAll() map[Feature]FeatureSpec { f.lock.Lock() versionedSpecs := f.GetAllVersioned() emuVer := f.EmulationVersion() + minCompatVer := f.MinCompatibilityVersion() f.lock.Unlock() for k, v := range versionedSpecs { - spec := featureSpecAtEmulationVersion(v, emuVer) + spec := featureSpecAtEmulationAndMinCompatVersion(v, emuVer, minCompatVer) if spec.PreRelease == PreAlpha { // The feature is not available at the emulation version. continue @@ -512,12 +785,16 @@ func (f *featureGate) GetAllVersioned() map[Feature]VersionedSpecs { } func (f *featureGate) SetEmulationVersion(emulationVersion *version.Version) error { - if emulationVersion.EqualTo(f.EmulationVersion()) { + return f.SetEmulationVersionAndMinCompatibilityVersion(emulationVersion, emulationVersion.SubtractMinor(1)) +} + +func (f *featureGate) SetEmulationVersionAndMinCompatibilityVersion(emulationVersion *version.Version, minCompatibilityVersion *version.Version) error { + if emulationVersion.EqualTo(f.EmulationVersion()) && minCompatibilityVersion.EqualTo(f.MinCompatibilityVersion()) { return nil } f.lock.Lock() defer f.lock.Unlock() - klog.V(1).Infof("set feature gate emulationVersion to %s", emulationVersion.String()) + klog.V(1).Infof("set feature gate emulationVersion to %s, minCompatibilityVersion to %s", emulationVersion, minCompatibilityVersion) // Copy existing state enabledRaw := map[string]bool{} @@ -526,15 +803,15 @@ func (f *featureGate) SetEmulationVersion(emulationVersion *version.Version) err } // enabled map should be reset whenever emulationVersion is changed. enabled := map[Feature]bool{} - errs := f.unsafeSetFromMap(enabled, enabledRaw, emulationVersion) + errs := f.unsafeSetFromMap(enabled, enabledRaw, emulationVersion, minCompatibilityVersion) queriedFeatures := f.queriedFeatures.Load().(sets.Set[Feature]) known := f.known.Load().(map[Feature]VersionedSpecs) for feature := range queriedFeatures { - newVal := featureEnabled(feature, enabled, known, emulationVersion) - oldVal := featureEnabled(feature, f.enabled.Load().(map[Feature]bool), known, f.EmulationVersion()) + newVal := featureEnabled(feature, enabled, known, emulationVersion, minCompatibilityVersion) + oldVal := featureEnabled(feature, f.enabled.Load().(map[Feature]bool), known, f.EmulationVersion(), f.MinCompatibilityVersion()) if newVal != oldVal { - klog.Warningf("SetEmulationVersion will change already queried feature:%s from %v to %v", feature, oldVal, newVal) + klog.Warningf("SetEmulationVersionAndMinCompatibilityVersion will change already queried feature:%s from %v to %v", feature, oldVal, newVal) } } @@ -542,6 +819,7 @@ func (f *featureGate) SetEmulationVersion(emulationVersion *version.Version) err // Persist changes f.enabled.Store(enabled) f.emulationVersion.Store(emulationVersion) + f.minCompatibilityVersion.Store(minCompatibilityVersion) f.queriedFeatures.Store(sets.Set[Feature]{}) } return utilerrors.NewAggregate(errs) @@ -551,11 +829,15 @@ func (f *featureGate) EmulationVersion() *version.Version { return f.emulationVersion.Load() } +func (f *featureGate) MinCompatibilityVersion() *version.Version { + return f.minCompatibilityVersion.Load() +} + // featureSpec returns the featureSpec at the EmulationVersion if the key exists, an error otherwise. // This is useful to keep multiple implementations of a feature based on the PreRelease or Version info. func (f *featureGate) featureSpec(key Feature) (FeatureSpec, error) { if v, ok := f.known.Load().(map[Feature]VersionedSpecs)[key]; ok { - featureSpec := f.featureSpecAtEmulationVersion(v) + featureSpec := f.featureSpecAtEmulationAndMinCompatVersion(v) return *featureSpec, nil } return FeatureSpec{}, fmt.Errorf("feature %q is not registered in FeatureGate %q", key, f.featureGateName) @@ -572,13 +854,14 @@ func (f *featureGate) unsafeRecordQueried(key Feature) { f.queriedFeatures.Store(newQueriedFeatures) } -func featureEnabled(key Feature, enabled map[Feature]bool, known map[Feature]VersionedSpecs, emulationVersion *version.Version) bool { +func featureEnabled(key Feature, enabled map[Feature]bool, known map[Feature]VersionedSpecs, emulationVersion, minCompatibilityVersion *version.Version) bool { // check explicitly set enabled list if v, ok := enabled[key]; ok { return v } if v, ok := known[key]; ok { - return featureSpecAtEmulationVersion(v, emulationVersion).Default + featureSpec := featureSpecAtEmulationAndMinCompatVersion(v, emulationVersion, minCompatibilityVersion) + return featureSpec.Default } panic(fmt.Errorf("feature %q is not registered in FeatureGate", key)) @@ -587,21 +870,28 @@ func featureEnabled(key Feature, enabled map[Feature]bool, known map[Feature]Ver // Enabled returns true if the key is enabled. If the key is not known, this call will panic. func (f *featureGate) Enabled(key Feature) bool { // TODO: ideally we should lock the feature gate in this call to be safe, need to evaluate how much performance impact locking would have. - v := featureEnabled(key, f.enabled.Load().(map[Feature]bool), f.known.Load().(map[Feature]VersionedSpecs), f.EmulationVersion()) + v := featureEnabled(key, f.enabled.Load().(map[Feature]bool), f.known.Load().(map[Feature]VersionedSpecs), f.EmulationVersion(), f.MinCompatibilityVersion()) f.unsafeRecordQueried(key) return v } -func (f *featureGate) featureSpecAtEmulationVersion(v VersionedSpecs) *FeatureSpec { - return featureSpecAtEmulationVersion(v, f.EmulationVersion()) +func (f *featureGate) featureSpecAtEmulationAndMinCompatVersion(v VersionedSpecs) *FeatureSpec { + return featureSpecAtEmulationAndMinCompatVersion(v, f.EmulationVersion(), f.MinCompatibilityVersion()) } -func featureSpecAtEmulationVersion(v VersionedSpecs, emulationVersion *version.Version) *FeatureSpec { +func featureSpecAtEmulationAndMinCompatVersion(v VersionedSpecs, emulationVersion, minCompatibilityVersion *version.Version) *FeatureSpec { i := len(v) - 1 + if minCompatibilityVersion == nil { + minCompatibilityVersion = emulationVersion.SubtractMinor(1) + } for ; i >= 0; i-- { if v[i].Version.GreaterThan(emulationVersion) { continue } + specMinCompatibilityVersion := v[i].MinCompatibilityVersion + if specMinCompatibilityVersion != nil && !minCompatibilityVersion.AtLeast(specMinCompatibilityVersion) { + continue + } return &v[i] } return &FeatureSpec{ @@ -647,11 +937,26 @@ func (f *featureGate) KnownFeatures() []string { known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v[0].PreRelease, v[0].Default)) continue } - featureSpec := f.featureSpecAtEmulationVersion(v) - if featureSpec.PreRelease == GA || featureSpec.PreRelease == Deprecated || featureSpec.PreRelease == PreAlpha { + featureSpec := f.featureSpecAtEmulationAndMinCompatVersion(v) + featureSpecStr := featureSpec.HelpString(k, false) + featureSpecMinCompatMode := featureSpecAtEmulationAndMinCompatVersion(v, f.EmulationVersion(), f.EmulationVersion()) + featureSpecMinCompatModeStr := featureSpecMinCompatMode.HelpString(k, true) + if reflect.DeepEqual(featureSpec, featureSpecMinCompatMode) { + if featureSpecStr != "" { + known = append(known, featureSpecStr) + } continue } - known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, featureSpec.PreRelease, featureSpec.Default)) + components := []string{} + if featureSpecStr != "" { + components = append(components, featureSpecStr) + } + if featureSpecMinCompatModeStr != "" { + components = append(components, featureSpecMinCompatModeStr) + } + if len(components) > 0 { + known = append(known, strings.Join(components, ", or ")) + } } sort.Strings(known) return known @@ -661,7 +966,7 @@ func (f *featureGate) KnownFeatures() []string { // and resets all the enabled status of the new feature gate. // This is useful for creating a new instance of feature gate without inheriting all the enabled configurations of the base feature gate. func (f *featureGate) DeepCopyAndReset() MutableVersionedFeatureGate { - fg := NewVersionedFeatureGate(f.EmulationVersion()) + fg := NewVersionedFeatureGateWithMinCompatibility(f.EmulationVersion(), f.MinCompatibilityVersion()) known := f.GetAllVersioned() fg.known.Store(known) return fg @@ -683,6 +988,10 @@ func (f *featureGate) DeepCopy() MutableVersionedFeatureGate { for k, v := range f.enabledRaw.Load().(map[string]bool) { enabledRaw[k] = v } + dependencies := map[Feature][]Feature{} + for k, v := range *f.dependencies.Load() { + dependencies[k] = append([]Feature{}, v...) + } // Construct a new featureGate around the copied state. // Note that specialFeatures is treated as immutable by convention, @@ -692,8 +1001,10 @@ func (f *featureGate) DeepCopy() MutableVersionedFeatureGate { closed: f.closed, } fg.emulationVersion.Store(f.EmulationVersion()) + fg.minCompatibilityVersion.Store(f.MinCompatibilityVersion()) fg.known.Store(known) fg.enabled.Store(enabled) + fg.dependencies.Store(&dependencies) fg.enabledRaw.Store(enabledRaw) fg.queriedFeatures.Store(sets.Set[Feature]{}) return fg diff --git a/vendor/k8s.io/component-base/featuregate/registry.go b/vendor/k8s.io/component-base/featuregate/registry.go deleted file mode 100644 index cf35403da..000000000 --- a/vendor/k8s.io/component-base/featuregate/registry.go +++ /dev/null @@ -1,454 +0,0 @@ -/* -Copyright 2024 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 featuregate - -import ( - "fmt" - "sort" - "strings" - "sync" - - "github.com/spf13/pflag" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/version" - cliflag "k8s.io/component-base/cli/flag" - baseversion "k8s.io/component-base/version" - "k8s.io/klog/v2" -) - -// DefaultComponentGlobalsRegistry is the global var to store the effective versions and feature gates for all components for easy access. -// Example usage: -// // register the component effective version and feature gate first -// _, _ = utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(utilversion.DefaultKubeComponent, utilversion.DefaultKubeEffectiveVersion(), utilfeature.DefaultMutableFeatureGate) -// wardleEffectiveVersion := utilversion.NewEffectiveVersion("1.2") -// wardleFeatureGate := featuregate.NewFeatureGate() -// utilruntime.Must(utilversion.DefaultComponentGlobalsRegistry.Register(apiserver.WardleComponentName, wardleEffectiveVersion, wardleFeatureGate, false)) -// -// cmd := &cobra.Command{ -// ... -// // call DefaultComponentGlobalsRegistry.Set() in PersistentPreRunE -// PersistentPreRunE: func(*cobra.Command, []string) error { -// if err := utilversion.DefaultComponentGlobalsRegistry.Set(); err != nil { -// return err -// } -// ... -// }, -// RunE: func(c *cobra.Command, args []string) error { -// // call utilversion.DefaultComponentGlobalsRegistry.Validate() somewhere -// }, -// } -// -// flags := cmd.Flags() -// // add flags -// utilversion.DefaultComponentGlobalsRegistry.AddFlags(flags) -var DefaultComponentGlobalsRegistry ComponentGlobalsRegistry = NewComponentGlobalsRegistry() - -const ( - DefaultKubeComponent = "kube" - - klogLevel = 2 -) - -type VersionMapping func(from *version.Version) *version.Version - -// ComponentGlobals stores the global variables for a component for easy access. -type ComponentGlobals struct { - effectiveVersion baseversion.MutableEffectiveVersion - featureGate MutableVersionedFeatureGate - - // emulationVersionMapping contains the mapping from the emulation version of this component - // to the emulation version of another component. - emulationVersionMapping map[string]VersionMapping - // dependentEmulationVersion stores whether or not this component's EmulationVersion is dependent through mapping on another component. - // If true, the emulation version cannot be set from the flag, or version mapping from another component. - dependentEmulationVersion bool - // minCompatibilityVersionMapping contains the mapping from the min compatibility version of this component - // to the min compatibility version of another component. - minCompatibilityVersionMapping map[string]VersionMapping - // dependentMinCompatibilityVersion stores whether or not this component's MinCompatibilityVersion is dependent through mapping on another component - // If true, the min compatibility version cannot be set from the flag, or version mapping from another component. - dependentMinCompatibilityVersion bool -} - -type ComponentGlobalsRegistry interface { - // EffectiveVersionFor returns the EffectiveVersion registered under the component. - // Returns nil if the component is not registered. - EffectiveVersionFor(component string) baseversion.EffectiveVersion - // FeatureGateFor returns the FeatureGate registered under the component. - // Returns nil if the component is not registered. - FeatureGateFor(component string) FeatureGate - // Register registers the EffectiveVersion and FeatureGate for a component. - // returns error if the component is already registered. - Register(component string, effectiveVersion baseversion.MutableEffectiveVersion, featureGate MutableVersionedFeatureGate) error - // ComponentGlobalsOrRegister would return the registered global variables for the component if it already exists in the registry. - // Otherwise, the provided variables would be registered under the component, and the same variables would be returned. - ComponentGlobalsOrRegister(component string, effectiveVersion baseversion.MutableEffectiveVersion, featureGate MutableVersionedFeatureGate) (baseversion.MutableEffectiveVersion, MutableVersionedFeatureGate) - // AddFlags adds flags of "--emulated-version" and "--feature-gates" - AddFlags(fs *pflag.FlagSet) - // Set sets the flags for all global variables for all components registered. - Set() error - // SetFallback calls Set() if it has never been called. - SetFallback() error - // Validate calls the Validate() function for all the global variables for all components registered. - Validate() []error - // Reset removes all stored ComponentGlobals, configurations, and version mappings. - Reset() - // SetEmulationVersionMapping sets the mapping from the emulation version of one component - // to the emulation version of another component. - // Once set, the emulation version of the toComponent will be determined by the emulation version of the fromComponent, - // and cannot be set from cmd flags anymore. - // For a given component, its emulation version can only depend on one other component, no multiple dependency is allowed. - SetEmulationVersionMapping(fromComponent, toComponent string, f VersionMapping) error -} - -type componentGlobalsRegistry struct { - componentGlobals map[string]*ComponentGlobals - mutex sync.RWMutex - // list of component name to emulation version set from the flag. - emulationVersionConfig []string - // map of component name to the list of feature gates set from the flag. - featureGatesConfig map[string][]string - // set stores if the Set() function for the registry is already called. - set bool -} - -func NewComponentGlobalsRegistry() *componentGlobalsRegistry { - return &componentGlobalsRegistry{ - componentGlobals: make(map[string]*ComponentGlobals), - emulationVersionConfig: nil, - featureGatesConfig: nil, - } -} - -func (r *componentGlobalsRegistry) Reset() { - r.mutex.Lock() - defer r.mutex.Unlock() - r.componentGlobals = make(map[string]*ComponentGlobals) - r.emulationVersionConfig = nil - r.featureGatesConfig = nil - r.set = false -} - -func (r *componentGlobalsRegistry) EffectiveVersionFor(component string) baseversion.EffectiveVersion { - r.mutex.RLock() - defer r.mutex.RUnlock() - globals, ok := r.componentGlobals[component] - if !ok { - return nil - } - return globals.effectiveVersion -} - -func (r *componentGlobalsRegistry) FeatureGateFor(component string) FeatureGate { - r.mutex.RLock() - defer r.mutex.RUnlock() - globals, ok := r.componentGlobals[component] - if !ok { - return nil - } - return globals.featureGate -} - -func (r *componentGlobalsRegistry) unsafeRegister(component string, effectiveVersion baseversion.MutableEffectiveVersion, featureGate MutableVersionedFeatureGate) error { - if _, ok := r.componentGlobals[component]; ok { - return fmt.Errorf("component globals of %s already registered", component) - } - if featureGate != nil { - if err := featureGate.SetEmulationVersion(effectiveVersion.EmulationVersion()); err != nil { - return err - } - } - c := ComponentGlobals{ - effectiveVersion: effectiveVersion, - featureGate: featureGate, - emulationVersionMapping: make(map[string]VersionMapping), - minCompatibilityVersionMapping: make(map[string]VersionMapping), - } - r.componentGlobals[component] = &c - return nil -} - -func (r *componentGlobalsRegistry) Register(component string, effectiveVersion baseversion.MutableEffectiveVersion, featureGate MutableVersionedFeatureGate) error { - if effectiveVersion == nil { - return fmt.Errorf("cannot register nil effectiveVersion") - } - r.mutex.Lock() - defer r.mutex.Unlock() - return r.unsafeRegister(component, effectiveVersion, featureGate) -} - -func (r *componentGlobalsRegistry) ComponentGlobalsOrRegister(component string, effectiveVersion baseversion.MutableEffectiveVersion, featureGate MutableVersionedFeatureGate) (baseversion.MutableEffectiveVersion, MutableVersionedFeatureGate) { - r.mutex.Lock() - defer r.mutex.Unlock() - globals, ok := r.componentGlobals[component] - if ok { - return globals.effectiveVersion, globals.featureGate - } - utilruntime.Must(r.unsafeRegister(component, effectiveVersion, featureGate)) - return effectiveVersion, featureGate -} - -func (r *componentGlobalsRegistry) unsafeKnownFeatures() []string { - var known []string - for component, globals := range r.componentGlobals { - if globals.featureGate == nil { - continue - } - for _, f := range globals.featureGate.KnownFeatures() { - known = append(known, component+":"+f) - } - } - sort.Strings(known) - return known -} - -func (r *componentGlobalsRegistry) unsafeVersionFlagOptions(isEmulation bool) []string { - var vs []string - for component, globals := range r.componentGlobals { - binaryVer := globals.effectiveVersion.BinaryVersion() - if isEmulation { - if globals.dependentEmulationVersion { - continue - } - // emulated version could be between binaryMajor.{binaryMinor} and binaryMajor.{binaryMinor} - // TODO: change to binaryMajor.{binaryMinor-1} and binaryMajor.{binaryMinor} in 1.32 - vs = append(vs, fmt.Sprintf("%s=%s..%s (default=%s)", component, - binaryVer.SubtractMinor(0).String(), binaryVer.String(), globals.effectiveVersion.EmulationVersion().String())) - } else { - if globals.dependentMinCompatibilityVersion { - continue - } - // min compatibility version could be between binaryMajor.{binaryMinor-1} and binaryMajor.{binaryMinor} - vs = append(vs, fmt.Sprintf("%s=%s..%s (default=%s)", component, - binaryVer.SubtractMinor(1).String(), binaryVer.String(), globals.effectiveVersion.MinCompatibilityVersion().String())) - } - } - sort.Strings(vs) - return vs -} - -func (r *componentGlobalsRegistry) AddFlags(fs *pflag.FlagSet) { - if r == nil { - return - } - r.mutex.Lock() - defer r.mutex.Unlock() - for _, globals := range r.componentGlobals { - if globals.featureGate != nil { - globals.featureGate.Close() - } - } - if r.emulationVersionConfig != nil || r.featureGatesConfig != nil { - klog.Warning("calling componentGlobalsRegistry.AddFlags more than once, the registry will be set by the latest flags") - } - r.emulationVersionConfig = []string{} - r.featureGatesConfig = make(map[string][]string) - - fs.StringSliceVar(&r.emulationVersionConfig, "emulated-version", r.emulationVersionConfig, ""+ - "The versions different components emulate their capabilities (APIs, features, ...) of.\n"+ - "If set, the component will emulate the behavior of this version instead of the underlying binary version.\n"+ - "Version format could only be major.minor, for example: '--emulated-version=wardle=1.2,kube=1.31'. Options are:\n"+strings.Join(r.unsafeVersionFlagOptions(true), "\n")+ - "If the component is not specified, defaults to \"kube\"") - - fs.Var(cliflag.NewColonSeparatedMultimapStringStringAllowDefaultEmptyKey(&r.featureGatesConfig), "feature-gates", "Comma-separated list of component:key=value pairs that describe feature gates for alpha/experimental features of different components.\n"+ - "If the component is not specified, defaults to \"kube\". This flag can be repeatedly invoked. For example: --feature-gates 'wardle:featureA=true,wardle:featureB=false' --feature-gates 'kube:featureC=true'"+ - "Options are:\n"+strings.Join(r.unsafeKnownFeatures(), "\n")) -} - -type componentVersion struct { - component string - ver *version.Version -} - -// getFullEmulationVersionConfig expands the given version config with version registered version mapping, -// and returns the map of component to Version. -func (r *componentGlobalsRegistry) getFullEmulationVersionConfig( - versionConfigMap map[string]*version.Version) (map[string]*version.Version, error) { - result := map[string]*version.Version{} - setQueue := []componentVersion{} - for comp, ver := range versionConfigMap { - if _, ok := r.componentGlobals[comp]; !ok { - return result, fmt.Errorf("component not registered: %s", comp) - } - klog.V(klogLevel).Infof("setting version %s=%s", comp, ver.String()) - setQueue = append(setQueue, componentVersion{comp, ver}) - } - for len(setQueue) > 0 { - cv := setQueue[0] - if _, visited := result[cv.component]; visited { - return result, fmt.Errorf("setting version of %s more than once, probably version mapping loop", cv.component) - } - setQueue = setQueue[1:] - result[cv.component] = cv.ver - for toComp, f := range r.componentGlobals[cv.component].emulationVersionMapping { - toVer := f(cv.ver) - if toVer == nil { - return result, fmt.Errorf("got nil version from mapping of %s=%s to component:%s", cv.component, cv.ver.String(), toComp) - } - klog.V(klogLevel).Infof("setting version %s=%s from version mapping of %s=%s", toComp, toVer.String(), cv.component, cv.ver.String()) - setQueue = append(setQueue, componentVersion{toComp, toVer}) - } - } - return result, nil -} - -func toVersionMap(versionConfig []string) (map[string]*version.Version, error) { - m := map[string]*version.Version{} - for _, compVer := range versionConfig { - // default to "kube" of component is not specified - k := "kube" - v := compVer - if strings.Contains(compVer, "=") { - arr := strings.SplitN(compVer, "=", 2) - if len(arr) != 2 { - return m, fmt.Errorf("malformed pair, expect string=string") - } - k = strings.TrimSpace(arr[0]) - v = strings.TrimSpace(arr[1]) - } - ver, err := version.Parse(v) - if err != nil { - return m, err - } - if ver.Patch() != 0 { - return m, fmt.Errorf("patch version not allowed, got: %s=%s", k, ver.String()) - } - if existingVer, ok := m[k]; ok { - return m, fmt.Errorf("duplicate version flag, %s=%s and %s=%s", k, existingVer.String(), k, ver.String()) - } - m[k] = ver - } - return m, nil -} - -func (r *componentGlobalsRegistry) SetFallback() error { - r.mutex.Lock() - set := r.set - r.mutex.Unlock() - if set { - return nil - } - klog.Warning("setting componentGlobalsRegistry in SetFallback. We recommend calling componentGlobalsRegistry.Set()" + - " right after parsing flags to avoid using feature gates before their final values are set by the flags.") - return r.Set() -} - -func (r *componentGlobalsRegistry) Set() error { - r.mutex.Lock() - defer r.mutex.Unlock() - r.set = true - emulationVersionConfigMap, err := toVersionMap(r.emulationVersionConfig) - if err != nil { - return err - } - for comp := range emulationVersionConfigMap { - if _, ok := r.componentGlobals[comp]; !ok { - return fmt.Errorf("component not registered: %s", comp) - } - // only components without any dependencies can be set from the flag. - if r.componentGlobals[comp].dependentEmulationVersion { - return fmt.Errorf("EmulationVersion of %s is set by mapping, cannot set it by flag", comp) - } - } - if emulationVersions, err := r.getFullEmulationVersionConfig(emulationVersionConfigMap); err != nil { - return err - } else { - for comp, ver := range emulationVersions { - r.componentGlobals[comp].effectiveVersion.SetEmulationVersion(ver) - } - } - // Set feature gate emulation version before setting feature gate flag values. - for comp, globals := range r.componentGlobals { - if globals.featureGate == nil { - continue - } - klog.V(klogLevel).Infof("setting %s:feature gate emulation version to %s", comp, globals.effectiveVersion.EmulationVersion().String()) - if err := globals.featureGate.SetEmulationVersion(globals.effectiveVersion.EmulationVersion()); err != nil { - return err - } - } - for comp, fg := range r.featureGatesConfig { - if comp == "" { - if _, ok := r.featureGatesConfig[DefaultKubeComponent]; ok { - return fmt.Errorf("set kube feature gates with default empty prefix or kube: prefix consistently, do not mix use") - } - comp = DefaultKubeComponent - } - if _, ok := r.componentGlobals[comp]; !ok { - return fmt.Errorf("component not registered: %s", comp) - } - featureGate := r.componentGlobals[comp].featureGate - if featureGate == nil { - return fmt.Errorf("component featureGate not registered: %s", comp) - } - flagVal := strings.Join(fg, ",") - klog.V(klogLevel).Infof("setting %s:feature-gates=%s", comp, flagVal) - if err := featureGate.Set(flagVal); err != nil { - return err - } - } - return nil -} - -func (r *componentGlobalsRegistry) Validate() []error { - var errs []error - r.mutex.Lock() - defer r.mutex.Unlock() - for _, globals := range r.componentGlobals { - errs = append(errs, globals.effectiveVersion.Validate()...) - if globals.featureGate != nil { - errs = append(errs, globals.featureGate.Validate()...) - } - } - return errs -} - -func (r *componentGlobalsRegistry) SetEmulationVersionMapping(fromComponent, toComponent string, f VersionMapping) error { - if f == nil { - return nil - } - klog.V(klogLevel).Infof("setting EmulationVersion mapping from %s to %s", fromComponent, toComponent) - r.mutex.Lock() - defer r.mutex.Unlock() - if _, ok := r.componentGlobals[fromComponent]; !ok { - return fmt.Errorf("component not registered: %s", fromComponent) - } - if _, ok := r.componentGlobals[toComponent]; !ok { - return fmt.Errorf("component not registered: %s", toComponent) - } - // check multiple dependency - if r.componentGlobals[toComponent].dependentEmulationVersion { - return fmt.Errorf("mapping of %s already exists from another component", toComponent) - } - r.componentGlobals[toComponent].dependentEmulationVersion = true - - versionMapping := r.componentGlobals[fromComponent].emulationVersionMapping - if _, ok := versionMapping[toComponent]; ok { - return fmt.Errorf("EmulationVersion from %s to %s already exists", fromComponent, toComponent) - } - versionMapping[toComponent] = f - klog.V(klogLevel).Infof("setting the default EmulationVersion of %s based on mapping from the default EmulationVersion of %s", fromComponent, toComponent) - defaultFromVersion := r.componentGlobals[fromComponent].effectiveVersion.EmulationVersion() - emulationVersions, err := r.getFullEmulationVersionConfig(map[string]*version.Version{fromComponent: defaultFromVersion}) - if err != nil { - return err - } - for comp, ver := range emulationVersions { - r.componentGlobals[comp].effectiveVersion.SetEmulationVersion(ver) - } - return nil -} diff --git a/vendor/k8s.io/component-base/metrics/counter.go b/vendor/k8s.io/component-base/metrics/counter.go index 8a7dd7154..6805bf940 100644 --- a/vendor/k8s.io/component-base/metrics/counter.go +++ b/vendor/k8s.io/component-base/metrics/counter.go @@ -30,7 +30,6 @@ import ( // Counter is our internal representation for our wrapping struct around prometheus // counters. Counter implements both kubeCollector and CounterMetric. type Counter struct { - ctx context.Context CounterMetric *CounterOpts lazyMetric @@ -40,12 +39,10 @@ type Counter struct { // The implementation of the Metric interface is expected by testutil.GetCounterMetricValue. var _ Metric = &Counter{} -// All supported exemplar metric types implement the metricWithExemplar interface. -var _ metricWithExemplar = &Counter{} - // exemplarCounterMetric holds a context to extract exemplar labels from, and a counter metric to attach them to. It implements the metricWithExemplar interface. type exemplarCounterMetric struct { - *Counter + ctx context.Context + delegate CounterMetric } // NewCounter returns an object which satisfies the kubeCollector and CounterMetric interfaces. @@ -107,26 +104,12 @@ func (c *Counter) initializeDeprecatedMetric() { // WithContext allows the normal Counter metric to pass in context. func (c *Counter) WithContext(ctx context.Context) CounterMetric { - c.ctx = ctx - return c.CounterMetric -} - -// withExemplar initializes the exemplarMetric object and sets the exemplar value. -func (c *Counter) withExemplar(v float64) { - (&exemplarCounterMetric{c}).withExemplar(v) -} - -func (c *Counter) Add(v float64) { - c.withExemplar(v) -} - -func (c *Counter) Inc() { - c.withExemplar(1) + return &exemplarCounterMetric{ctx: ctx, delegate: c.CounterMetric} } -// withExemplar attaches an exemplar to the metric. -func (e *exemplarCounterMetric) withExemplar(v float64) { - if m, ok := e.CounterMetric.(prometheus.ExemplarAdder); ok { +// Add attaches an exemplar to the metric and then calls the delegate. +func (e *exemplarCounterMetric) Add(v float64) { + if m, ok := e.delegate.(prometheus.ExemplarAdder); ok { maybeSpanCtx := trace.SpanContextFromContext(e.ctx) if maybeSpanCtx.IsValid() && maybeSpanCtx.IsSampled() { exemplarLabels := prometheus.Labels{ @@ -138,7 +121,12 @@ func (e *exemplarCounterMetric) withExemplar(v float64) { } } - e.CounterMetric.Add(v) + e.delegate.Add(v) +} + +// Inc attaches an exemplar to the metric and then calls the delegate. +func (e *exemplarCounterMetric) Inc() { + e.Add(1) } // CounterVec is the internal representation of our wrapping struct around prometheus @@ -212,17 +200,19 @@ func (v *CounterVec) WithLabelValues(lvs ...string) CounterMetric { if !v.IsCreated() { return noop // return no-op counter } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label values to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainToAllowedList(v.originalLabels, lvs) - } - allowListLock.RUnlock() - }) } return v.CounterVec.WithLabelValues(lvs...) @@ -236,18 +226,19 @@ func (v *CounterVec) With(labels map[string]string) CounterMetric { if !v.IsCreated() { return noop // return no-op counter } + + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainLabelMap(labels) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainLabelMap(labels) - } - allowListLock.RUnlock() - }) } + return v.CounterVec.With(labels) } diff --git a/vendor/k8s.io/component-base/metrics/desc.go b/vendor/k8s.io/component-base/metrics/desc.go index 2ca9cfa7c..f921be615 100644 --- a/vendor/k8s.io/component-base/metrics/desc.go +++ b/vendor/k8s.io/component-base/metrics/desc.go @@ -107,20 +107,18 @@ func (d *Desc) DeprecatedVersion() *semver.Version { } -func (d *Desc) determineDeprecationStatus(version semver.Version) { - selfVersion := d.DeprecatedVersion() - if selfVersion == nil { +func (d *Desc) determineDeprecationStatus(currentVersion semver.Version) { + deprecatedVersion := d.DeprecatedVersion() + if deprecatedVersion == nil { return } d.markDeprecationOnce.Do(func() { - if selfVersion.LTE(version) { - d.isDeprecated = true - } - if ShouldShowHidden() { - klog.Warningf("Hidden metrics(%s) have been manually overridden, showing this very deprecated metric.", d.fqName) - return - } - if shouldHide(&version, selfVersion) { + d.isDeprecated = isDeprecated(currentVersion, *deprecatedVersion) + if shouldHide(d.stabilityLevel, ¤tVersion, deprecatedVersion) { + if shouldShowHidden() { + klog.Warningf("Hidden metrics(%s) have been manually overridden, showing this very deprecated metric.", d.fqName) + return + } // TODO(RainbowMango): Remove this log temporarily. https://github.com/kubernetes/kubernetes/issues/85369 // klog.Warningf("This metric(%s) has been deprecated for more than one release, hiding.", d.fqName) d.isHidden = true diff --git a/vendor/k8s.io/component-base/metrics/gauge.go b/vendor/k8s.io/component-base/metrics/gauge.go index 0621560d0..20982f67d 100644 --- a/vendor/k8s.io/component-base/metrics/gauge.go +++ b/vendor/k8s.io/component-base/metrics/gauge.go @@ -143,20 +143,33 @@ func (v *GaugeVec) WithLabelValuesChecked(lvs ...string) (GaugeMetric, error) { } return noop, errNotRegistered // return no-op gauge } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label values to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainToAllowedList(v.originalLabels, lvs) - } - allowListLock.RUnlock() - }) } - elt, err := v.GaugeVec.GetMetricWithLabelValues(lvs...) - return elt, err + + return v.GetMetricWithLabelValues(lvs...) +} + +func (v *GaugeVec) DeleteLabelValuesChecked(lvs ...string) (bool, error) { + if !v.IsCreated() { + if v.IsHidden() { + return false, nil + } + return false, errNotRegistered + } + + return v.GaugeVec.DeleteLabelValues(lvs...), nil } // Default Prometheus Vec behavior is that member extraction results in creation of a new element @@ -182,6 +195,14 @@ func (v *GaugeVec) WithLabelValues(lvs ...string) GaugeMetric { panic(err) } +func (v *GaugeVec) DeleteLabelValues(lvs ...string) bool { + ans, err := v.DeleteLabelValuesChecked(lvs...) + if err == nil || ErrIsNotRegistered(err) { + return ans + } + panic(err) +} + func (v *GaugeVec) WithChecked(labels map[string]string) (GaugeMetric, error) { if !v.IsCreated() { if v.IsHidden() { @@ -189,20 +210,22 @@ func (v *GaugeVec) WithChecked(labels map[string]string) (GaugeMetric, error) { } return noop, errNotRegistered // return no-op gauge } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label map to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainLabelMap(labels) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainLabelMap(labels) - } - allowListLock.RUnlock() - }) } - elt, err := v.GaugeVec.GetMetricWith(labels) - return elt, err + + return v.GetMetricWith(labels) } // With returns the GaugeMetric for the given Labels map (the label names diff --git a/vendor/k8s.io/component-base/metrics/histogram.go b/vendor/k8s.io/component-base/metrics/histogram.go index 3065486ab..73b56d1b8 100644 --- a/vendor/k8s.io/component-base/metrics/histogram.go +++ b/vendor/k8s.io/component-base/metrics/histogram.go @@ -28,7 +28,6 @@ import ( // Histogram is our internal representation for our wrapping struct around prometheus // histograms. Summary implements both kubeCollector and ObserverMetric type Histogram struct { - ctx context.Context ObserverMetric *HistogramOpts lazyMetric @@ -37,7 +36,8 @@ type Histogram struct { // exemplarHistogramMetric holds a context to extract exemplar labels from, and a historgram metric to attach them to. It implements the metricWithExemplar interface. type exemplarHistogramMetric struct { - *Histogram + ctx context.Context + delegate ObserverMetric } type exemplarHistogramVec struct { @@ -45,18 +45,9 @@ type exemplarHistogramVec struct { observer prometheus.Observer } -func (h *Histogram) Observe(v float64) { - h.withExemplar(v) -} - -// withExemplar initializes the exemplarMetric object and sets the exemplar value. -func (h *Histogram) withExemplar(v float64) { - (&exemplarHistogramMetric{h}).withExemplar(v) -} - -// withExemplar attaches an exemplar to the metric. -func (e *exemplarHistogramMetric) withExemplar(v float64) { - if m, ok := e.Histogram.ObserverMetric.(prometheus.ExemplarObserver); ok { +// Observe attaches an exemplar to the metric and then calls the delegate. +func (e *exemplarHistogramMetric) Observe(v float64) { + if m, ok := e.delegate.(prometheus.ExemplarObserver); ok { maybeSpanCtx := trace.SpanContextFromContext(e.ctx) if maybeSpanCtx.IsValid() && maybeSpanCtx.IsSampled() { exemplarLabels := prometheus.Labels{ @@ -68,7 +59,7 @@ func (e *exemplarHistogramMetric) withExemplar(v float64) { } } - e.ObserverMetric.Observe(v) + e.delegate.Observe(v) } // NewHistogram returns an object which is Histogram-like. However, nothing @@ -113,8 +104,7 @@ func (h *Histogram) initializeDeprecatedMetric() { // WithContext allows the normal Histogram metric to pass in context. The context is no-op now. func (h *Histogram) WithContext(ctx context.Context) ObserverMetric { - h.ctx = ctx - return h.ObserverMetric + return &exemplarHistogramMetric{ctx: ctx, delegate: h.ObserverMetric} } // HistogramVec is the internal representation of our wrapping struct around prometheus @@ -181,17 +171,19 @@ func (v *HistogramVec) WithLabelValues(lvs ...string) ObserverMetric { if !v.IsCreated() { return noop } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label values to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainToAllowedList(v.originalLabels, lvs) - } - allowListLock.RUnlock() - }) } return v.HistogramVec.WithLabelValues(lvs...) } @@ -204,18 +196,21 @@ func (v *HistogramVec) With(labels map[string]string) ObserverMetric { if !v.IsCreated() { return noop } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label map to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainLabelMap(labels) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainLabelMap(labels) - } - allowListLock.RUnlock() - }) } + return v.HistogramVec.With(labels) } diff --git a/vendor/k8s.io/component-base/metrics/legacyregistry/registry.go b/vendor/k8s.io/component-base/metrics/legacyregistry/registry.go index 64a430b79..c8c0bc08f 100644 --- a/vendor/k8s.io/component-base/metrics/legacyregistry/registry.go +++ b/vendor/k8s.io/component-base/metrics/legacyregistry/registry.go @@ -90,3 +90,8 @@ func CustomMustRegister(cs ...metrics.StableCollector) { prometheus.MustRegister(c) } } + +// GetProcessStart return processStart value +func GetProcessStart() time.Time { + return processStart +} diff --git a/vendor/k8s.io/component-base/metrics/metric.go b/vendor/k8s.io/component-base/metrics/metric.go index c8b083995..cb40a856c 100644 --- a/vendor/k8s.io/component-base/metrics/metric.go +++ b/vendor/k8s.io/component-base/metrics/metric.go @@ -18,6 +18,7 @@ package metrics import ( "sync" + "sync/atomic" "github.com/blang/semver/v4" "github.com/prometheus/client_golang/prometheus" @@ -65,8 +66,8 @@ with the kubeCollector itself as an argument. */ type lazyMetric struct { fqName string - isDeprecated bool - isHidden bool + isDeprecated atomic.Bool + isHidden atomic.Bool isCreated bool createLock sync.RWMutex markDeprecationOnce sync.Once @@ -99,41 +100,39 @@ func (r *lazyMetric) lazyInit(self kubeCollector, fqName string) { // Disclaimer: disabling a metric via a CLI flag has higher precedence than // deprecation and will override show-hidden-metrics for the explicitly // disabled metric. -func (r *lazyMetric) preprocessMetric(version semver.Version) { +func (r *lazyMetric) preprocessMetric(currentVersion semver.Version) { disabledMetricsLock.RLock() defer disabledMetricsLock.RUnlock() // disabling metrics is higher in precedence than showing hidden metrics if _, ok := disabledMetrics[r.fqName]; ok { - r.isHidden = true + r.isHidden.Store(true) return } - selfVersion := r.self.DeprecatedVersion() - if selfVersion == nil { + deprecatedVersion := r.self.DeprecatedVersion() + if deprecatedVersion == nil { return } r.markDeprecationOnce.Do(func() { - if selfVersion.LTE(version) { - r.isDeprecated = true - } + r.isDeprecated.Store(isDeprecated(currentVersion, *deprecatedVersion)) - if ShouldShowHidden() { - klog.Warningf("Hidden metrics (%s) have been manually overridden, showing this very deprecated metric.", r.fqName) - return - } - if shouldHide(&version, selfVersion) { + if shouldHide(r.stabilityLevel, ¤tVersion, deprecatedVersion) { + if shouldShowHidden() { + klog.Warningf("Hidden metrics (%s) have been manually overridden, showing this very deprecated metric.", r.fqName) + return + } // TODO(RainbowMango): Remove this log temporarily. https://github.com/kubernetes/kubernetes/issues/85369 // klog.Warningf("This metric has been deprecated for more than one release, hiding.") - r.isHidden = true + r.isHidden.Store(true) } }) } func (r *lazyMetric) IsHidden() bool { - return r.isHidden + return r.isHidden.Load() } func (r *lazyMetric) IsDeprecated() bool { - return r.isDeprecated + return r.isDeprecated.Load() } // Create forces the initialization of metric which has been deferred until @@ -176,8 +175,8 @@ func (r *lazyMetric) ClearState() { r.createLock.Lock() defer r.createLock.Unlock() - r.isDeprecated = false - r.isHidden = false + r.isDeprecated.Store(false) + r.isHidden.Store(false) r.isCreated = false r.markDeprecationOnce = sync.Once{} r.createOnce = sync.Once{} @@ -210,11 +209,6 @@ func (c *selfCollector) Collect(ch chan<- prometheus.Metric) { ch <- c.metric } -// metricWithExemplar is an interface that knows how to attach an exemplar to certain supported metric types. -type metricWithExemplar interface { - withExemplar(v float64) -} - // no-op vecs for convenience var noopCounterVec = &prometheus.CounterVec{} var noopHistogramVec = &prometheus.HistogramVec{} diff --git a/vendor/k8s.io/component-base/metrics/opts.go b/vendor/k8s.io/component-base/metrics/opts.go index 43015169e..247b9fd1c 100644 --- a/vendor/k8s.io/component-base/metrics/opts.go +++ b/vendor/k8s.io/component-base/metrics/opts.go @@ -26,10 +26,10 @@ import ( "github.com/prometheus/client_golang/prometheus" + yaml "go.yaml.in/yaml/v2" "k8s.io/apimachinery/pkg/util/sets" promext "k8s.io/component-base/metrics/prometheusextension" "k8s.io/klog/v2" - yaml "sigs.k8s.io/yaml/goyaml.v2" ) var ( diff --git a/vendor/k8s.io/component-base/metrics/registry.go b/vendor/k8s.io/component-base/metrics/registry.go index 203813e81..aa12f2e2c 100644 --- a/vendor/k8s.io/component-base/metrics/registry.go +++ b/vendor/k8s.io/component-base/metrics/registry.go @@ -17,7 +17,7 @@ limitations under the License. package metrics import ( - "fmt" + "strings" "sync" "sync/atomic" @@ -71,19 +71,58 @@ var ( ) ) -// shouldHide be used to check if a specific metric with deprecated version should be hidden +// shouldHide is used to check if a specific metric with deprecated version should be hidden // according to metrics deprecation lifecycle. -func shouldHide(currentVersion *semver.Version, deprecatedVersion *semver.Version) bool { - guardVersion, err := semver.Make(fmt.Sprintf("%d.%d.0", currentVersion.Major, currentVersion.Minor)) - if err != nil { - panic("failed to make version from current version") +func shouldHide(stabilityLevel StabilityLevel, currentVersion *semver.Version, deprecatedVersion *semver.Version) bool { + hiddenMinor := deprecatedVersion.Minor + deprecationPeriodMinorVersions(stabilityLevel) + + switch { + case deprecatedVersion.Major < currentVersion.Major: + return true + case deprecatedVersion.Major > currentVersion.Major: + return false + + // deprecatedVersion.Major == currentVersion.Major + case hiddenMinor < currentVersion.Minor: + return true + case hiddenMinor > currentVersion.Minor: + return false + + // deprecatedVersion.Minor == currentVersion.Minor + case strings.Contains(currentVersion.String(), "alpha.0"): + // Wait until we're past the alpha.0 period of a minor development cycle to hide metrics whose deprecation period ends in that minor version. + // See discussion in https://github.com/kubernetes/kubernetes/issues/133429#issuecomment-3165551443 + return false + default: + return true } +} + +// getDeprecationReleaseWindow returns the number of minor releases a metric should be served +// after its deprecated version, based on its stability level. +func deprecationPeriodMinorVersions(stabilityLevel StabilityLevel) uint64 { + switch stabilityLevel { + case STABLE: + return 3 + case BETA: + return 1 + default: // ALPHA, INTERNAL + return 0 + } +} - if deprecatedVersion.LT(guardVersion) { +// isDeprecated returns true if the current version, ignoring pre-release tags, +// is greater than or equal to the deprecated version. +func isDeprecated(currentVersion, deprecatedVersion semver.Version) bool { + switch { + case currentVersion.Major < deprecatedVersion.Major: + return false + case currentVersion.Major > deprecatedVersion.Major: return true } - return false + // currentVersion.Major == deprecatedVersion.Major + return currentVersion.Minor >= deprecatedVersion.Minor } // ValidateShowHiddenMetricsVersion checks invalid version for which show hidden metrics. @@ -117,10 +156,10 @@ func SetShowHidden() { }) } -// ShouldShowHidden returns whether showing hidden deprecated metrics +// shouldShowHidden returns whether showing hidden deprecated metrics // is enabled. While the primary usecase for this is internal (to determine // registration behavior) this can also be used to introspect -func ShouldShowHidden() bool { +func shouldShowHidden() bool { return showHidden.Load() } diff --git a/vendor/k8s.io/component-base/metrics/summary.go b/vendor/k8s.io/component-base/metrics/summary.go index f1af12175..82898e60e 100644 --- a/vendor/k8s.io/component-base/metrics/summary.go +++ b/vendor/k8s.io/component-base/metrics/summary.go @@ -33,7 +33,7 @@ const ( // Summary is our internal representation for our wrapping struct around prometheus // summaries. Summary implements both kubeCollector and ObserverMetric // -// DEPRECATED: as per the metrics overhaul KEP +// Deprecated: as per the metrics overhaul KEP type Summary struct { ObserverMetric *SummaryOpts @@ -44,7 +44,7 @@ type Summary struct { // NewSummary returns an object which is Summary-like. However, nothing // will be measured until the summary is registered somewhere. // -// DEPRECATED: as per the metrics overhaul KEP +// Deprecated: as per the metrics overhaul KEP func NewSummary(opts *SummaryOpts) *Summary { opts.StabilityLevel.setDefaults() @@ -91,7 +91,7 @@ func (s *Summary) WithContext(ctx context.Context) ObserverMetric { // SummaryVec is the internal representation of our wrapping struct around prometheus // summaryVecs. // -// DEPRECATED: as per the metrics overhaul KEP +// Deprecated: as per the metrics overhaul KEP type SummaryVec struct { *prometheus.SummaryVec *SummaryOpts @@ -105,7 +105,7 @@ type SummaryVec struct { // and only members extracted after // registration will actually measure anything. // -// DEPRECATED: as per the metrics overhaul KEP +// Deprecated: as per the metrics overhaul KEP func NewSummaryVec(opts *SummaryOpts, labels []string) *SummaryVec { opts.StabilityLevel.setDefaults() @@ -154,17 +154,19 @@ func (v *SummaryVec) WithLabelValues(lvs ...string) ObserverMetric { if !v.IsCreated() { return noop } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label values to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainToAllowedList(v.originalLabels, lvs) - } - allowListLock.RUnlock() - }) } return v.SummaryVec.WithLabelValues(lvs...) } @@ -177,17 +179,17 @@ func (v *SummaryVec) With(labels map[string]string) ObserverMetric { if !v.IsCreated() { return noop } + + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainLabelMap(labels) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainLabelMap(labels) - } - allowListLock.RUnlock() - }) } return v.SummaryVec.With(labels) } diff --git a/vendor/k8s.io/component-base/metrics/timing_histogram.go b/vendor/k8s.io/component-base/metrics/timing_histogram.go index 4fc757473..5399f8d1b 100644 --- a/vendor/k8s.io/component-base/metrics/timing_histogram.go +++ b/vendor/k8s.io/component-base/metrics/timing_histogram.go @@ -169,17 +169,19 @@ func (v *TimingHistogramVec) WithLabelValuesChecked(lvs ...string) (GaugeMetric, } return noop, errNotRegistered } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label values to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainToAllowedList(v.originalLabels, lvs) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainToAllowedList(v.originalLabels, lvs) - } - allowListLock.RUnlock() - }) } ops, err := v.TimingHistogramVec.GetMetricWithLabelValues(lvs...) if err != nil { @@ -217,19 +219,24 @@ func (v *TimingHistogramVec) WithChecked(labels map[string]string) (GaugeMetric, } return noop, errNotRegistered } + + // Initialize label allow lists if not already initialized + v.initializeLabelAllowListsOnce.Do(func() { + allowListLock.RLock() + if allowList, ok := labelValueAllowLists[v.FQName()]; ok { + v.LabelValueAllowLists = allowList + } + allowListLock.RUnlock() + }) + + // Constrain label map to allowed values if v.LabelValueAllowLists != nil { v.LabelValueAllowLists.ConstrainLabelMap(labels) - } else { - v.initializeLabelAllowListsOnce.Do(func() { - allowListLock.RLock() - if allowList, ok := labelValueAllowLists[v.FQName()]; ok { - v.LabelValueAllowLists = allowList - allowList.ConstrainLabelMap(labels) - } - allowListLock.RUnlock() - }) } ops, err := v.TimingHistogramVec.GetMetricWith(labels) + if err != nil { + return noop, err + } return ops.(GaugeMetric), err } diff --git a/vendor/k8s.io/component-base/version/base.go b/vendor/k8s.io/component-base/version/base.go index 46500118a..94da79c08 100644 --- a/vendor/k8s.io/component-base/version/base.go +++ b/vendor/k8s.io/component-base/version/base.go @@ -24,15 +24,9 @@ package version // information from git. // // If you are looking at these fields in the git tree, they look -// strange. They are modified on the fly by the build process. The +// strange. They are set by the build process with ldflags -X. The // in-tree values are dummy values used for "git archive", which also // works for GitHub tar downloads. -// -// When releasing a new Kubernetes version, this file is updated by -// build/mark_new_version.sh to reflect the new version, and then a -// git annotated tag (using format vX.Y where X == Major version and Y -// == Minor version) is created to point to the commit that updates -// component-base/version/base.go var ( // TODO: Deprecate gitMajor and gitMinor, use only gitVersion // instead. First step in deprecation, keep the fields but make @@ -66,5 +60,5 @@ const ( // DefaultKubeBinaryVersion is the hard coded k8 binary version based on the latest K8s release. // It is supposed to be consistent with gitMajor and gitMinor, except for local tests, where gitMajor and gitMinor are "". // Should update for each minor release! - DefaultKubeBinaryVersion = "1.32" + DefaultKubeBinaryVersion = "1.35" ) diff --git a/vendor/k8s.io/component-base/version/version.go b/vendor/k8s.io/component-base/version/version.go index 99d368534..c71ccf196 100644 --- a/vendor/k8s.io/component-base/version/version.go +++ b/vendor/k8s.io/component-base/version/version.go @@ -19,43 +19,14 @@ package version import ( "fmt" "runtime" - "sync/atomic" - "k8s.io/apimachinery/pkg/util/version" apimachineryversion "k8s.io/apimachinery/pkg/version" ) -type EffectiveVersion interface { - BinaryVersion() *version.Version - EmulationVersion() *version.Version - MinCompatibilityVersion() *version.Version - EqualTo(other EffectiveVersion) bool - String() string - Validate() []error -} - -type MutableEffectiveVersion interface { - EffectiveVersion - Set(binaryVersion, emulationVersion, minCompatibilityVersion *version.Version) - SetEmulationVersion(emulationVersion *version.Version) - SetMinCompatibilityVersion(minCompatibilityVersion *version.Version) -} - -type effectiveVersion struct { - // When true, BinaryVersion() returns the current binary version - useDefaultBuildBinaryVersion atomic.Bool - // Holds the last binary version stored in Set() - binaryVersion atomic.Pointer[version.Version] - // If the emulationVersion is set by the users, it could only contain major and minor versions. - // In tests, emulationVersion could be the same as the binary version, or set directly, - // which can have "alpha" as pre-release to continue serving expired apis while we clean up the test. - emulationVersion atomic.Pointer[version.Version] - // minCompatibilityVersion could only contain major and minor versions. - minCompatibilityVersion atomic.Pointer[version.Version] -} - // Get returns the overall codebase version. It's for detecting // what code a binary was built from. +// The caller should use BinaryMajor and BinaryMinor to determine +// the binary version. The Major and Minor fields are still set by git version for backwards compatibility. func Get() apimachineryversion.Info { // These variables typically come from -ldflags settings and in // their absence fallback to the settings in ./base.go @@ -71,129 +42,3 @@ func Get() apimachineryversion.Info { Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } - -func (m *effectiveVersion) BinaryVersion() *version.Version { - if m.useDefaultBuildBinaryVersion.Load() { - return defaultBuildBinaryVersion() - } - return m.binaryVersion.Load() -} - -func (m *effectiveVersion) EmulationVersion() *version.Version { - ver := m.emulationVersion.Load() - if ver != nil { - // Emulation version can have "alpha" as pre-release to continue serving expired apis while we clean up the test. - // The pre-release should not be accessible to the users. - return ver.WithPreRelease(m.BinaryVersion().PreRelease()) - } - return ver -} - -func (m *effectiveVersion) MinCompatibilityVersion() *version.Version { - return m.minCompatibilityVersion.Load() -} - -func (m *effectiveVersion) EqualTo(other EffectiveVersion) bool { - return m.BinaryVersion().EqualTo(other.BinaryVersion()) && m.EmulationVersion().EqualTo(other.EmulationVersion()) && m.MinCompatibilityVersion().EqualTo(other.MinCompatibilityVersion()) -} - -func (m *effectiveVersion) String() string { - if m == nil { - return "" - } - return fmt.Sprintf("{BinaryVersion: %s, EmulationVersion: %s, MinCompatibilityVersion: %s}", - m.BinaryVersion().String(), m.EmulationVersion().String(), m.MinCompatibilityVersion().String()) -} - -func majorMinor(ver *version.Version) *version.Version { - if ver == nil { - return ver - } - return version.MajorMinor(ver.Major(), ver.Minor()) -} - -func (m *effectiveVersion) Set(binaryVersion, emulationVersion, minCompatibilityVersion *version.Version) { - m.binaryVersion.Store(binaryVersion) - m.useDefaultBuildBinaryVersion.Store(false) - m.emulationVersion.Store(majorMinor(emulationVersion)) - m.minCompatibilityVersion.Store(majorMinor(minCompatibilityVersion)) -} - -func (m *effectiveVersion) SetEmulationVersion(emulationVersion *version.Version) { - m.emulationVersion.Store(majorMinor(emulationVersion)) -} - -func (m *effectiveVersion) SetMinCompatibilityVersion(minCompatibilityVersion *version.Version) { - m.minCompatibilityVersion.Store(majorMinor(minCompatibilityVersion)) -} - -func (m *effectiveVersion) Validate() []error { - var errs []error - // Validate only checks the major and minor versions. - binaryVersion := m.BinaryVersion().WithPatch(0) - emulationVersion := m.emulationVersion.Load() - minCompatibilityVersion := m.minCompatibilityVersion.Load() - - // emulationVersion can only be 1.{binaryMinor-1}...1.{binaryMinor}. - maxEmuVer := binaryVersion - minEmuVer := binaryVersion.SubtractMinor(1) - if emulationVersion.GreaterThan(maxEmuVer) || emulationVersion.LessThan(minEmuVer) { - errs = append(errs, fmt.Errorf("emulation version %s is not between [%s, %s]", emulationVersion.String(), minEmuVer.String(), maxEmuVer.String())) - } - // minCompatibilityVersion can only be 1.{binaryMinor-1} for alpha. - maxCompVer := binaryVersion.SubtractMinor(1) - minCompVer := binaryVersion.SubtractMinor(1) - if minCompatibilityVersion.GreaterThan(maxCompVer) || minCompatibilityVersion.LessThan(minCompVer) { - errs = append(errs, fmt.Errorf("minCompatibilityVersion version %s is not between [%s, %s]", minCompatibilityVersion.String(), minCompVer.String(), maxCompVer.String())) - } - return errs -} - -func newEffectiveVersion(binaryVersion *version.Version, useDefaultBuildBinaryVersion bool) MutableEffectiveVersion { - effective := &effectiveVersion{} - compatVersion := binaryVersion.SubtractMinor(1) - effective.Set(binaryVersion, binaryVersion, compatVersion) - effective.useDefaultBuildBinaryVersion.Store(useDefaultBuildBinaryVersion) - return effective -} - -func NewEffectiveVersion(binaryVer string) MutableEffectiveVersion { - if binaryVer == "" { - return &effectiveVersion{} - } - binaryVersion := version.MustParse(binaryVer) - return newEffectiveVersion(binaryVersion, false) -} - -func defaultBuildBinaryVersion() *version.Version { - verInfo := Get() - return version.MustParse(verInfo.String()).WithInfo(verInfo) -} - -// DefaultBuildEffectiveVersion returns the MutableEffectiveVersion based on the -// current build information. -func DefaultBuildEffectiveVersion() MutableEffectiveVersion { - binaryVersion := defaultBuildBinaryVersion() - if binaryVersion.Major() == 0 && binaryVersion.Minor() == 0 { - return DefaultKubeEffectiveVersion() - } - return newEffectiveVersion(binaryVersion, true) -} - -// DefaultKubeEffectiveVersion returns the MutableEffectiveVersion based on the -// latest K8s release. -func DefaultKubeEffectiveVersion() MutableEffectiveVersion { - binaryVersion := version.MustParse(DefaultKubeBinaryVersion).WithInfo(Get()) - return newEffectiveVersion(binaryVersion, false) -} - -// ValidateKubeEffectiveVersion validates the EmulationVersion is equal to the binary version at 1.31 for kube components. -// emulationVersion is introduced in 1.31, so it is only allowed to be equal to the binary version at 1.31. -func ValidateKubeEffectiveVersion(effectiveVersion EffectiveVersion) error { - binaryVersion := version.MajorMinor(effectiveVersion.BinaryVersion().Major(), effectiveVersion.BinaryVersion().Minor()) - if binaryVersion.EqualTo(version.MajorMinor(1, 31)) && !effectiveVersion.EmulationVersion().EqualTo(binaryVersion) { - return fmt.Errorf("emulation version needs to be equal to binary version(%s) in compatibility-version alpha, got %s", - binaryVersion.String(), effectiveVersion.EmulationVersion().String()) - } - return nil -} diff --git a/vendor/k8s.io/klog/v2/README.md b/vendor/k8s.io/klog/v2/README.md index d45cbe172..a680beb40 100644 --- a/vendor/k8s.io/klog/v2/README.md +++ b/vendor/k8s.io/klog/v2/README.md @@ -48,8 +48,6 @@ How to use klog - For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md)) - See our documentation on [pkg.go.dev/k8s.io](https://pkg.go.dev/k8s.io/klog). -**NOTE**: please use the newer go versions that support semantic import versioning in modules, ideally go 1.11.4 or greater. - ### Coexisting with klog/v2 See [this example](examples/coexist_klog_v1_and_v2/) to see how to coexist with both klog/v1 and klog/v2. diff --git a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues.go b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues.go index d1a4751c9..73f91ea50 100644 --- a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues.go +++ b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues.go @@ -20,7 +20,9 @@ import ( "bytes" "encoding/json" "fmt" + "slices" "strconv" + "strings" "github.com/go-logr/logr" ) @@ -51,139 +53,157 @@ func WithValues(oldKV, newKV []interface{}) []interface{} { return kv } -// MergeKVs deduplicates elements provided in two key/value slices. -// -// Keys in each slice are expected to be unique, so duplicates can only occur -// when the first and second slice contain the same key. When that happens, the -// key/value pair from the second slice is used. The first slice must be well-formed -// (= even key/value pairs). The second one may have a missing value, in which -// case the special "missing value" is added to the result. -func MergeKVs(first, second []interface{}) []interface{} { - maxLength := len(first) + (len(second)+1)/2*2 - if maxLength == 0 { - // Nothing to do at all. - return nil - } - - if len(first) == 0 && len(second)%2 == 0 { - // Nothing to be overridden, second slice is well-formed - // and can be used directly. - return second - } - - // Determine which keys are in the second slice so that we can skip - // them when iterating over the first one. The code intentionally - // favors performance over completeness: we assume that keys are string - // constants and thus compare equal when the string values are equal. A - // string constant being overridden by, for example, a fmt.Stringer is - // not handled. - overrides := map[interface{}]bool{} - for i := 0; i < len(second); i += 2 { - overrides[second[i]] = true - } - merged := make([]interface{}, 0, maxLength) - for i := 0; i+1 < len(first); i += 2 { - key := first[i] - if overrides[key] { - continue - } - merged = append(merged, key, first[i+1]) - } - merged = append(merged, second...) - if len(merged)%2 != 0 { - merged = append(merged, missingValue) - } - return merged -} - type Formatter struct { AnyToStringHook AnyToStringFunc } type AnyToStringFunc func(v interface{}) string -// MergeKVsInto is a variant of MergeKVs which directly formats the key/value -// pairs into a buffer. -func (f Formatter) MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) { - if len(first) == 0 && len(second) == 0 { - // Nothing to do at all. - return - } +const missingValue = "(MISSING)" - if len(first) == 0 && len(second)%2 == 0 { - // Nothing to be overridden, second slice is well-formed - // and can be used directly. - for i := 0; i < len(second); i += 2 { - f.KVFormat(b, second[i], second[i+1]) - } - return - } +func FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) { + Formatter{}.FormatKVs(b, kvs...) +} - // Determine which keys are in the second slice so that we can skip - // them when iterating over the first one. The code intentionally - // favors performance over completeness: we assume that keys are string - // constants and thus compare equal when the string values are equal. A - // string constant being overridden by, for example, a fmt.Stringer is - // not handled. - overrides := map[interface{}]bool{} - for i := 0; i < len(second); i += 2 { - overrides[second[i]] = true - } - for i := 0; i < len(first); i += 2 { - key := first[i] - if overrides[key] { - continue +// FormatKVs formats all key/value pairs such that the output contains no +// duplicates ("last one wins"). +func (f Formatter) FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) { + // De-duplication is done by optimistically formatting all key value + // pairs and then cutting out the output of those key/value pairs which + // got overwritten later. + // + // In the common case of no duplicates, the only overhead is tracking + // previous keys. This uses a slice with a simple linear search because + // the number of entries is typically so low that allocating a map or + // keeping a sorted slice with binary search aren't justified. + // + // Using a fixed size here makes the Go compiler use the stack as + // initial backing store for the slice, which is crucial for + // performance. + existing := make([]obsoleteKV, 0, 32) + obsolete := make([]interval, 0, 32) // Sorted by start index. + for _, keysAndValues := range kvs { + for i := 0; i < len(keysAndValues); i += 2 { + var v interface{} + k := keysAndValues[i] + if i+1 < len(keysAndValues) { + v = keysAndValues[i+1] + } else { + v = missingValue + } + var e obsoleteKV + e.start = b.Len() + e.key = f.KVFormat(b, k, v) + e.end = b.Len() + i := findObsoleteEntry(existing, e.key) + if i >= 0 { + data := b.Bytes() + if bytes.Compare(data[existing[i].start:existing[i].end], data[e.start:e.end]) == 0 { + // The new entry gets obsoleted because it's identical. + // This has the advantage that key/value pairs from + // a WithValues call always come first, even if the same + // pair gets added again later. This makes different log + // entries more consistent. + // + // The new entry has a higher start index and thus can be appended. + obsolete = append(obsolete, e.interval) + } else { + // The old entry gets obsoleted because it's value is different. + // + // Sort order is not guaranteed, we have to insert at the right place. + index, _ := slices.BinarySearchFunc(obsolete, existing[i].interval, func(a, b interval) int { return a.start - b.start }) + obsolete = slices.Insert(obsolete, index, existing[i].interval) + existing[i].interval = e.interval + } + } else { + // Instead of appending at the end and doing a + // linear search in findEntry, we could keep + // the slice sorted by key and do a binary search. + // + // Above: + // i, ok := slices.BinarySearchFunc(existing, e, func(a, b entry) int { return strings.Compare(a.key, b.key) }) + // Here: + // existing = slices.Insert(existing, i, e) + // + // But that adds a dependency on the slices package + // and made performance slightly worse, presumably + // because the cost of shifting entries around + // did not pay of with faster lookups. + existing = append(existing, e) + } } - f.KVFormat(b, key, first[i+1]) } - // Round down. - l := len(second) - l = l / 2 * 2 - for i := 1; i < l; i += 2 { - f.KVFormat(b, second[i-1], second[i]) - } - if len(second)%2 == 1 { - f.KVFormat(b, second[len(second)-1], missingValue) - } -} -func MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) { - Formatter{}.MergeAndFormatKVs(b, first, second) -} + // If we need to remove some obsolete key/value pairs then move the memory. + if len(obsolete) > 0 { + // Potentially the next remaining output (might itself be obsolete). + from := obsolete[0].end + // Next obsolete entry. + nextObsolete := 1 + // This is the source buffer, before truncation. + all := b.Bytes() + b.Truncate(obsolete[0].start) -const missingValue = "(MISSING)" + for nextObsolete < len(obsolete) { + if from == obsolete[nextObsolete].start { + // Skip also the next obsolete key/value. + from = obsolete[nextObsolete].end + nextObsolete++ + continue + } -// KVListFormat serializes all key/value pairs into the provided buffer. -// A space gets inserted before the first pair and between each pair. -func (f Formatter) KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) { - for i := 0; i < len(keysAndValues); i += 2 { - var v interface{} - k := keysAndValues[i] - if i+1 < len(keysAndValues) { - v = keysAndValues[i+1] - } else { - v = missingValue + // Preserve some output. Write uses copy, which + // explicitly allows source and destination to overlap. + // That could happen here. + valid := all[from:obsolete[nextObsolete].start] + b.Write(valid) + from = obsolete[nextObsolete].end + nextObsolete++ } - f.KVFormat(b, k, v) + // Copy end of buffer. + valid := all[from:] + b.Write(valid) } } -func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) { - Formatter{}.KVListFormat(b, keysAndValues...) +type obsoleteKV struct { + key string + interval +} + +// interval includes the start and excludes the end. +type interval struct { + start int + end int } -func KVFormat(b *bytes.Buffer, k, v interface{}) { - Formatter{}.KVFormat(b, k, v) +func findObsoleteEntry(entries []obsoleteKV, key string) int { + for i, entry := range entries { + if entry.key == key { + return i + } + } + return -1 } // formatAny is the fallback formatter for a value. It supports a hook (for // example, for YAML encoding) and itself uses JSON encoding. func (f Formatter) formatAny(b *bytes.Buffer, v interface{}) { - b.WriteRune('=') if f.AnyToStringHook != nil { - b.WriteString(f.AnyToStringHook(v)) + str := f.AnyToStringHook(v) + if strings.Contains(str, "\n") { + // If it's multi-line, then pass it through writeStringValue to get start/end delimiters, + // which separates it better from any following key/value pair. + writeStringValue(b, str) + return + } + // Otherwise put it directly after the separator, on the same lime, + // The assumption is that the hook returns something where start/end are obvious. + b.WriteRune('=') + b.WriteString(str) return } + b.WriteRune('=') formatAsJSON(b, v) } diff --git a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_no_slog.go b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_no_slog.go index d9c7d1546..b8c7e443d 100644 --- a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_no_slog.go +++ b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_no_slog.go @@ -28,7 +28,7 @@ import ( // KVFormat serializes one key/value pair into the provided buffer. // A space gets inserted before the pair. -func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) { +func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string { // This is the version without slog support. Must be kept in sync with // the version in keyvalues_slog.go. @@ -37,13 +37,15 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) { // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments // for the sake of performance. Keys with spaces, // special characters, etc. will break parsing. + var key string if sK, ok := k.(string); ok { // Avoid one allocation when the key is a string, which // normally it should be. - b.WriteString(sK) + key = sK } else { - b.WriteString(fmt.Sprintf("%s", k)) + key = fmt.Sprintf("%s", k) } + b.WriteString(key) // The type checks are sorted so that more frequently used ones // come first because that is then faster in the common @@ -94,4 +96,6 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) { default: f.formatAny(b, v) } + + return key } diff --git a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_slog.go b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_slog.go index 89acf9772..8e0084364 100644 --- a/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_slog.go +++ b/vendor/k8s.io/klog/v2/internal/serialize/keyvalues_slog.go @@ -29,8 +29,8 @@ import ( ) // KVFormat serializes one key/value pair into the provided buffer. -// A space gets inserted before the pair. -func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) { +// A space gets inserted before the pair. It returns the key. +func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string { // This is the version without slog support. Must be kept in sync with // the version in keyvalues_slog.go. @@ -39,13 +39,15 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) { // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments // for the sake of performance. Keys with spaces, // special characters, etc. will break parsing. + var key string if sK, ok := k.(string); ok { // Avoid one allocation when the key is a string, which // normally it should be. - b.WriteString(sK) + key = sK } else { - b.WriteString(fmt.Sprintf("%s", k)) + key = fmt.Sprintf("%s", k) } + b.WriteString(key) // The type checks are sorted so that more frequently used ones // come first because that is then faster in the common @@ -112,6 +114,8 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) { default: f.formatAny(b, v) } + + return key } // generateJSON has the same preference for plain strings as KVFormat. diff --git a/vendor/k8s.io/klog/v2/klog.go b/vendor/k8s.io/klog/v2/klog.go index 47ec9466a..319ffbe24 100644 --- a/vendor/k8s.io/klog/v2/klog.go +++ b/vendor/k8s.io/klog/v2/klog.go @@ -58,15 +58,30 @@ // // -logtostderr=true // Logs are written to standard error instead of to files. -// This shortcuts most of the usual output routing: -// -alsologtostderr, -stderrthreshold and -log_dir have no -// effect and output redirection at runtime with SetOutput is -// ignored. +// By default, all logs are written regardless of severity +// (legacy behavior). To filter logs by severity when +// -logtostderr=true, set -legacy_stderr_threshold_behavior=false +// and use -stderrthreshold. +// With -legacy_stderr_threshold_behavior=true, +// -stderrthreshold has no effect. +// +// The following flags always have no effect: +// -alsologtostderr, -alsologtostderrthreshold, and -log_dir. +// Output redirection at runtime with SetOutput is also ignored. // -alsologtostderr=false // Logs are written to standard error as well as to files. +// -alsologtostderrthreshold=INFO +// Log events at or above this severity are logged to standard +// error when -alsologtostderr=true (no effect when -logtostderr=true). +// Default is INFO to maintain backward compatibility. // -stderrthreshold=ERROR // Log events at or above this severity are logged to standard -// error as well as to files. +// error as well as to files. When -logtostderr=true, this flag +// has no effect unless -legacy_stderr_threshold_behavior=false. +// -legacy_stderr_threshold_behavior=true +// If true, -stderrthreshold is ignored when -logtostderr=true +// (legacy behavior). If false, -stderrthreshold is honored even +// when -logtostderr=true, allowing severity-based filtering. // -log_dir="" // Log files will be written to this directory instead of the // default temporary directory. @@ -156,7 +171,7 @@ func (s *severityValue) Set(value string) error { } threshold = severity.Severity(v) } - logging.stderrThreshold.set(threshold) + s.set(threshold) return nil } @@ -409,6 +424,15 @@ var commandLine flag.FlagSet // init sets up the defaults and creates command line flags. func init() { + // Initialize severity thresholds + logging.stderrThreshold = severityValue{ + Severity: severity.ErrorLog, // Default stderrThreshold is ERROR. + } + logging.alsologtostderrthreshold = severityValue{ + Severity: severity.InfoLog, // Default alsologtostderrthreshold is INFO (to maintain backward compatibility). + } + logging.setVState(0, nil, false) + commandLine.StringVar(&logging.logDir, "log_dir", "", "If non-empty, write log files in this directory (no effect when -logtostderr=true)") commandLine.StringVar(&logging.logFile, "log_file", "", "If non-empty, use this log file (no effect when -logtostderr=true)") commandLine.Uint64Var(&logging.logFileMaxSizeMB, "log_file_max_size", 1800, @@ -416,16 +440,14 @@ func init() { "If the value is 0, the maximum file size is unlimited.") commandLine.BoolVar(&logging.toStderr, "logtostderr", true, "log to standard error instead of files") commandLine.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files (no effect when -logtostderr=true)") - logging.setVState(0, nil, false) + commandLine.BoolVar(&logging.legacyStderrThresholdBehavior, "legacy_stderr_threshold_behavior", true, "If true, stderrthreshold is ignored when logtostderr=true (legacy behavior). If false, stderrthreshold is honored even when logtostderr=true") commandLine.Var(&logging.verbosity, "v", "number for the log level verbosity") commandLine.BoolVar(&logging.addDirHeader, "add_dir_header", false, "If true, adds the file directory to the header of the log messages") commandLine.BoolVar(&logging.skipHeaders, "skip_headers", false, "If true, avoid header prefixes in the log messages") commandLine.BoolVar(&logging.oneOutput, "one_output", false, "If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)") commandLine.BoolVar(&logging.skipLogHeaders, "skip_log_headers", false, "If true, avoid headers when opening log files (no effect when -logtostderr=true)") - logging.stderrThreshold = severityValue{ - Severity: severity.ErrorLog, // Default stderrThreshold is ERROR. - } - commandLine.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true)") + commandLine.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true unless -legacy_stderr_threshold_behavior=false)") + commandLine.Var(&logging.alsologtostderrthreshold, "alsologtostderrthreshold", "logs at or above this threshold go to stderr when -alsologtostderr=true (no effect when -logtostderr=true)") commandLine.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") commandLine.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") @@ -470,11 +492,13 @@ type settings struct { // Boolean flags. Not handled atomically because the flag.Value interface // does not let us avoid the =true, and that shorthand is necessary for // compatibility. TODO: does this matter enough to fix? Seems unlikely. - toStderr bool // The -logtostderr flag. - alsoToStderr bool // The -alsologtostderr flag. + toStderr bool // The -logtostderr flag. + alsoToStderr bool // The -alsologtostderr flag. + legacyStderrThresholdBehavior bool // The -legacy_stderr_threshold_behavior flag. // Level flag. Handled atomically. - stderrThreshold severityValue // The -stderrthreshold flag. + stderrThreshold severityValue // The -stderrthreshold flag. + alsologtostderrthreshold severityValue // The -alsologtostderrthreshold flag. // Access to all of the following fields must be protected via a mutex. @@ -809,16 +833,21 @@ func (l *loggingT) infoS(logger *logWriter, filter LogFilter, depth int, msg str // printS is called from infoS and errorS if logger is not specified. // set log severity by s func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, keysAndValues ...interface{}) { - // Only create a new buffer if we don't have one cached. - b := buffer.GetBuffer() // The message is always quoted, even if it contains line breaks. // If developers want multi-line output, they should use a small, fixed // message and put the multi-line output into a value. - b.WriteString(strconv.Quote(msg)) + qMsg := make([]byte, 0, 1024) + qMsg = strconv.AppendQuote(qMsg, msg) + + // Only create a new buffer if we don't have one cached. + b := buffer.GetBuffer() + b.Write(qMsg) + + var errKV []interface{} if err != nil { - serialize.KVListFormat(&b.Buffer, "err", err) + errKV = []interface{}{"err", err} } - serialize.KVListFormat(&b.Buffer, keysAndValues...) + serialize.FormatKVs(&b.Buffer, errKV, keysAndValues) l.printDepth(s, nil, nil, depth+1, &b.Buffer) // Make the buffer available for reuse. buffer.PutBuffer(b) @@ -885,9 +914,25 @@ func (l *loggingT) output(s severity.Severity, logger *logWriter, buf *buffer.Bu } } } else if l.toStderr { - os.Stderr.Write(data) + // When logging to stderr only, check if we should filter by severity. + // This is controlled by the legacy_stderr_threshold_behavior flag. + if l.legacyStderrThresholdBehavior { + // Legacy behavior: always write to stderr, ignore stderrthreshold + os.Stderr.Write(data) + } else { + // New behavior: honor stderrthreshold even when logtostderr=true + if s >= l.stderrThreshold.get() { + os.Stderr.Write(data) + } + } } else { - if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { + // Write to stderr if any of these conditions are met: + // - alsoToStderr is set (legacy behavior) + // - alsologtostderr is set and severity meets alsologtostderrthreshold + // - alsologtostderr is not set and severity meets stderrThreshold + if alsoToStderr || + (l.alsoToStderr && s >= l.alsologtostderrthreshold.get()) || + (!l.alsoToStderr && s >= l.stderrThreshold.get()) { os.Stderr.Write(data) } diff --git a/vendor/k8s.io/klog/v2/klogr.go b/vendor/k8s.io/klog/v2/klogr.go index efec96fd4..6204c7bb4 100644 --- a/vendor/k8s.io/klog/v2/klogr.go +++ b/vendor/k8s.io/klog/v2/klogr.go @@ -53,7 +53,7 @@ func (l *klogger) Init(info logr.RuntimeInfo) { } func (l *klogger) Info(level int, msg string, kvList ...interface{}) { - merged := serialize.MergeKVs(l.values, kvList) + merged := serialize.WithValues(l.values, kvList) // Skip this function. VDepth(l.callDepth+1, Level(level)).InfoSDepth(l.callDepth+1, msg, merged...) } @@ -63,7 +63,7 @@ func (l *klogger) Enabled(level int) bool { } func (l *klogger) Error(err error, msg string, kvList ...interface{}) { - merged := serialize.MergeKVs(l.values, kvList) + merged := serialize.WithValues(l.values, kvList) ErrorSDepth(l.callDepth+1, err, msg, merged...) } diff --git a/vendor/k8s.io/klog/v2/klogr_slog.go b/vendor/k8s.io/klog/v2/klogr_slog.go index c77d7baaf..901e28dd3 100644 --- a/vendor/k8s.io/klog/v2/klogr_slog.go +++ b/vendor/k8s.io/klog/v2/klogr_slog.go @@ -63,12 +63,17 @@ func slogOutput(file string, line int, now time.Time, err error, s severity.Seve } // See printS. + qMsg := make([]byte, 0, 1024) + qMsg = strconv.AppendQuote(qMsg, msg) + b := buffer.GetBuffer() - b.WriteString(strconv.Quote(msg)) + b.Write(qMsg) + + var errKV []interface{} if err != nil { - serialize.KVListFormat(&b.Buffer, "err", err) + errKV = []interface{}{"err", err} } - serialize.KVListFormat(&b.Buffer, kvList...) + serialize.FormatKVs(&b.Buffer, errKV, kvList) // See print + header. buf := logging.formatHeader(s, file, line, now) diff --git a/vendor/modules.txt b/vendor/modules.txt index cfd97a04a..90ad94bf0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -779,17 +779,16 @@ k8s.io/client-go/util/flowcontrol k8s.io/client-go/util/homedir k8s.io/client-go/util/keyutil k8s.io/client-go/util/workqueue -# k8s.io/component-base v0.32.3 -## explicit; go 1.23.0 -k8s.io/component-base/cli/flag +# k8s.io/component-base v0.35.3 +## explicit; go 1.25.0 k8s.io/component-base/featuregate k8s.io/component-base/metrics k8s.io/component-base/metrics/legacyregistry k8s.io/component-base/metrics/prometheus/feature k8s.io/component-base/metrics/prometheusextension k8s.io/component-base/version -# k8s.io/klog/v2 v2.130.1 -## explicit; go 1.18 +# k8s.io/klog/v2 v2.140.0 +## explicit; go 1.21 k8s.io/klog/v2 k8s.io/klog/v2/internal/buffer k8s.io/klog/v2/internal/clock @@ -815,14 +814,14 @@ k8s.io/utils/clock k8s.io/utils/internal/third_party/forked/golang/net k8s.io/utils/net k8s.io/utils/ptr -# sigs.k8s.io/controller-runtime v0.20.4 -## explicit; go 1.23.0 +# sigs.k8s.io/controller-runtime v0.23.3 +## explicit; go 1.25.0 sigs.k8s.io/controller-runtime/pkg/client sigs.k8s.io/controller-runtime/pkg/client/apiutil sigs.k8s.io/controller-runtime/pkg/controller/controllerutil sigs.k8s.io/controller-runtime/pkg/log -# sigs.k8s.io/e2e-framework v0.6.0 -## explicit; go 1.23.0 +# sigs.k8s.io/e2e-framework v0.7.0 +## explicit; go 1.25.7 sigs.k8s.io/e2e-framework/klient sigs.k8s.io/e2e-framework/klient/conf sigs.k8s.io/e2e-framework/klient/decoder @@ -850,7 +849,7 @@ sigs.k8s.io/json/internal/golang/encoding/json ## explicit; go 1.18 sigs.k8s.io/randfill sigs.k8s.io/randfill/bytesource -# sigs.k8s.io/structured-merge-diff/v6 v6.3.0 +# sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 ## explicit; go 1.23 sigs.k8s.io/structured-merge-diff/v6/fieldpath sigs.k8s.io/structured-merge-diff/v6/merge @@ -860,4 +859,3 @@ sigs.k8s.io/structured-merge-diff/v6/value # sigs.k8s.io/yaml v1.6.0 ## explicit; go 1.22 sigs.k8s.io/yaml -sigs.k8s.io/yaml/goyaml.v2 diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go index 1d4ce264c..217990dec 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go @@ -161,15 +161,27 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi // RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated // with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from // baseConfig, if set, otherwise a default serializer will be set. -func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory, httpClient *http.Client) (rest.Interface, error) { +func RESTClientForGVK( + gvk schema.GroupVersionKind, + forceDisableProtoBuf bool, + isUnstructured bool, + baseConfig *rest.Config, + codecs serializer.CodecFactory, + httpClient *http.Client, +) (rest.Interface, error) { if httpClient == nil { return nil, fmt.Errorf("httpClient must not be nil, consider using rest.HTTPClientFor(c) to create a client") } - return rest.RESTClientForConfigAndClient(createRestConfig(gvk, isUnstructured, baseConfig, codecs), httpClient) + return rest.RESTClientForConfigAndClient(createRestConfig(gvk, forceDisableProtoBuf, isUnstructured, baseConfig, codecs), httpClient) } // createRestConfig copies the base config and updates needed fields for a new rest config. -func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory) *rest.Config { +func createRestConfig(gvk schema.GroupVersionKind, + forceDisableProtoBuf bool, + isUnstructured bool, + baseConfig *rest.Config, + codecs serializer.CodecFactory, +) *rest.Config { gv := gvk.GroupVersion() cfg := rest.CopyConfig(baseConfig) @@ -183,7 +195,7 @@ func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConf cfg.UserAgent = rest.DefaultKubernetesUserAgent() } // TODO(FillZpp): In the long run, we want to check discovery or something to make sure that this is actually true. - if cfg.ContentType == "" && !isUnstructured { + if cfg.ContentType == "" && !forceDisableProtoBuf { protobufSchemeLock.RLock() if protobufScheme.Recognizes(gvk) { cfg.ContentType = runtime.ContentTypeProtobuf @@ -219,7 +231,7 @@ func (t targetZeroingDecoder) Decode(data []byte, defaults *schema.GroupVersionK } // zero zeros the value of a pointer. -func zero(x interface{}) { +func zero(x any) { if x == nil { return } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/errors.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/errors.go index c216c49d2..b00e07123 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/errors.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/errors.go @@ -18,7 +18,7 @@ package apiutil import ( "fmt" - "sort" + "slices" "strings" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -38,7 +38,7 @@ func (e *ErrResourceDiscoveryFailed) Error() string { for k, v := range *e { subErrors = append(subErrors, fmt.Sprintf("%s: %v", k, v)) } - sort.Strings(subErrors) + slices.Sort(subErrors) return fmt.Sprintf("unable to retrieve the complete list of server APIs: %s", strings.Join(subErrors, ", ")) } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/applyconfigurations.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/applyconfigurations.go new file mode 100644 index 000000000..97192050f --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/applyconfigurations.go @@ -0,0 +1,75 @@ +/* +Copyright 2025 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 client + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" +) + +type unstructuredApplyConfiguration struct { + *unstructured.Unstructured +} + +func (u *unstructuredApplyConfiguration) IsApplyConfiguration() {} + +// ApplyConfigurationFromUnstructured creates a runtime.ApplyConfiguration from an *unstructured.Unstructured object. +// +// Do not use Unstructured objects here that were generated from API objects, as its impossible to tell +// if a zero value was explicitly set. +func ApplyConfigurationFromUnstructured(u *unstructured.Unstructured) runtime.ApplyConfiguration { + return &unstructuredApplyConfiguration{Unstructured: u} +} + +type applyconfigurationRuntimeObject struct { + runtime.ApplyConfiguration +} + +func (a *applyconfigurationRuntimeObject) GetObjectKind() schema.ObjectKind { + return a +} + +func (a *applyconfigurationRuntimeObject) GroupVersionKind() schema.GroupVersionKind { + return schema.GroupVersionKind{} +} + +func (a *applyconfigurationRuntimeObject) SetGroupVersionKind(gvk schema.GroupVersionKind) {} + +func (a *applyconfigurationRuntimeObject) DeepCopyObject() runtime.Object { + panic("applyconfigurationRuntimeObject does not support DeepCopyObject") +} + +func runtimeObjectFromApplyConfiguration(ac runtime.ApplyConfiguration) runtime.Object { + return &applyconfigurationRuntimeObject{ApplyConfiguration: ac} +} + +func gvkFromApplyConfiguration(ac applyConfiguration) (schema.GroupVersionKind, error) { + var gvk schema.GroupVersionKind + gv, err := schema.ParseGroupVersion(ptr.Deref(ac.GetAPIVersion(), "")) + if err != nil { + return gvk, fmt.Errorf("failed to parse %q as GroupVersion: %w", ptr.Deref(ac.GetAPIVersion(), ""), err) + } + gvk.Group = gv.Group + gvk.Version = gv.Version + gvk.Kind = ptr.Deref(ac.GetKind(), "") + + return gvk, nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go index 6d8744017..ad946daea 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go @@ -52,6 +52,25 @@ type Options struct { // DryRun instructs the client to only perform dry run requests. DryRun *bool + + // FieldOwner, if provided, sets the default field manager for all write operations + // (Create, Update, Patch, Apply) performed by this client. The field manager is used by + // the server for Server-Side Apply to track field ownership. + // For more details, see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#field-management + // + // This default can be overridden for a specific call by passing a [FieldOwner] option + // to the method. + FieldOwner string + + // FieldValidation sets the field validation strategy for all mutating operations performed by this client + // and subresource clients created from it. + // The exception are apply requests which are always strict, regardless of the FieldValidation setting. + // Available values for this option can be found in "k8s.io/apimachinery/pkg/apis/meta/v1" package and are: + // - FieldValidationIgnore + // - FieldValidationWarn + // - FieldValidationStrict + // For more details, see: https://kubernetes.io/docs/reference/using-api/api-concepts/#field-validation + FieldValidation string } // CacheOptions are options for creating a cache-backed client. @@ -74,8 +93,8 @@ type NewClientFunc func(config *rest.Config, options Options) (Client, error) // New returns a new Client using the provided config and Options. // // By default, the client surfaces warnings returned by the server. To -// suppress warnings, set config.WarningHandler = rest.NoWarnings{}. To -// define custom behavior, implement the rest.WarningHandler interface. +// suppress warnings, set config.WarningHandlerWithContext = rest.NoWarnings{}. To +// define custom behavior, implement the rest.WarningHandlerWithContext interface. // See [sigs.k8s.io/controller-runtime/pkg/log.KubeAPIWarningLogger] for // an example. // @@ -99,6 +118,13 @@ func New(config *rest.Config, options Options) (c Client, err error) { if err == nil && options.DryRun != nil && *options.DryRun { c = NewDryRunClient(c) } + if fo := options.FieldOwner; fo != "" { + c = WithFieldOwner(c, fo) + } + if fv := options.FieldValidation; fv != "" { + c = WithFieldValidation(c, FieldValidation(fv)) + } + return c, err } @@ -112,10 +138,9 @@ func newClient(config *rest.Config, options Options) (*client, error) { config.UserAgent = rest.DefaultKubernetesUserAgent() } - if config.WarningHandler == nil { + if config.WarningHandler == nil && config.WarningHandlerWithContext == nil { // By default, we surface warnings. - config.WarningHandler = log.NewKubeAPIWarningLogger( - log.Log.WithName("KubeAPIWarningLogger"), + config.WarningHandlerWithContext = log.NewKubeAPIWarningLogger( log.KubeAPIWarningLoggerOptions{ Deduplicate: false, }, @@ -152,8 +177,7 @@ func newClient(config *rest.Config, options Options) (*client, error) { mapper: options.Mapper, codecs: serializer.NewCodecFactory(options.Scheme), - structuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta), - unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta), + resourceByType: make(map[cacheKey]*resourceMeta), } rawMetaClient, err := metadata.NewForConfigAndClient(metadata.ConfigFor(config), options.HTTPClient) @@ -330,6 +354,16 @@ func (c *client) Patch(ctx context.Context, obj Object, patch Patch, opts ...Pat } } +func (c *client) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error { + switch obj := obj.(type) { + case *unstructuredApplyConfiguration: + defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) + return c.unstructuredClient.Apply(ctx, obj, opts...) + default: + return c.typedClient.Apply(ctx, obj, opts...) + } +} + // Get implements client.Client. func (c *client) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error { if isUncached, err := c.shouldBypassCache(obj); err != nil { @@ -535,6 +569,30 @@ func (po *SubResourcePatchOptions) ApplyToSubResourcePatch(o *SubResourcePatchOp } } +// SubResourceApplyOptions are the options for a subresource +// apply request. +type SubResourceApplyOptions struct { + ApplyOptions + SubResourceBody runtime.ApplyConfiguration +} + +// ApplyOpts applies the given options. +func (ao *SubResourceApplyOptions) ApplyOpts(opts []SubResourceApplyOption) *SubResourceApplyOptions { + for _, o := range opts { + o.ApplyToSubResourceApply(ao) + } + + return ao +} + +// ApplyToSubResourceApply applies the configuration on the given patch options. +func (ao *SubResourceApplyOptions) ApplyToSubResourceApply(o *SubResourceApplyOptions) { + ao.ApplyOptions.ApplyToApply(&o.ApplyOptions) + if ao.SubResourceBody != nil { + o.SubResourceBody = ao.SubResourceBody + } +} + func (sc *subResourceClient) Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error { switch obj.(type) { case runtime.Unstructured: @@ -586,3 +644,13 @@ func (sc *subResourceClient) Patch(ctx context.Context, obj Object, patch Patch, return sc.client.typedClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...) } } + +func (sc *subResourceClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...SubResourceApplyOption) error { + switch obj := obj.(type) { + case *unstructuredApplyConfiguration: + defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind()) + return sc.client.unstructuredClient.ApplySubResource(ctx, obj, sc.subResource, opts...) + default: + return sc.client.typedClient.ApplySubResource(ctx, obj, sc.subResource, opts...) + } +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client_rest_resources.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client_rest_resources.go index 2d0787952..d75d685cb 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client_rest_resources.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client_rest_resources.go @@ -17,16 +17,17 @@ limitations under the License. package client import ( + "fmt" "net/http" "strings" "sync" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/rest" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) @@ -47,22 +48,30 @@ type clientRestResources struct { // codecs are used to create a REST client for a gvk codecs serializer.CodecFactory - // structuredResourceByType stores structured type metadata - structuredResourceByType map[schema.GroupVersionKind]*resourceMeta - // unstructuredResourceByType stores unstructured type metadata - unstructuredResourceByType map[schema.GroupVersionKind]*resourceMeta - mu sync.RWMutex + // resourceByType stores type metadata + resourceByType map[cacheKey]*resourceMeta + + mu sync.RWMutex +} + +type cacheKey struct { + gvk schema.GroupVersionKind + forceDisableProtoBuf bool } // newResource maps obj to a Kubernetes Resource and constructs a client for that Resource. // If the object is a list, the resource represents the item's type instead. -func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, isUnstructured bool) (*resourceMeta, error) { +func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, + isList bool, + forceDisableProtoBuf bool, + isUnstructured bool, +) (*resourceMeta, error) { if strings.HasSuffix(gvk.Kind, "List") && isList { // if this was a list, treat it as a request for the item's resource gvk.Kind = gvk.Kind[:len(gvk.Kind)-4] } - client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, c.codecs, c.httpClient) + client, err := apiutil.RESTClientForGVK(gvk, forceDisableProtoBuf, isUnstructured, c.config, c.codecs, c.httpClient) if err != nil { return nil, err } @@ -73,52 +82,96 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i return &resourceMeta{Interface: client, mapping: mapping, gvk: gvk}, nil } +type applyConfiguration interface { + GetName() *string + GetNamespace() *string + GetKind() *string + GetAPIVersion() *string +} + // getResource returns the resource meta information for the given type of object. // If the object is a list, the resource represents the item's type instead. -func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, error) { - gvk, err := apiutil.GVKForObject(obj, c.scheme) - if err != nil { - return nil, err +func (c *clientRestResources) getResource(obj any) (*resourceMeta, error) { + var gvk schema.GroupVersionKind + var err error + var isApplyConfiguration bool + switch o := obj.(type) { + case runtime.Object: + gvk, err = apiutil.GVKForObject(o, c.scheme) + if err != nil { + return nil, err + } + case runtime.ApplyConfiguration: + ac, ok := o.(applyConfiguration) + if !ok { + return nil, fmt.Errorf("%T is a runtime.ApplyConfiguration but not an applyConfiguration", o) + } + gvk, err = gvkFromApplyConfiguration(ac) + if err != nil { + return nil, err + } + isApplyConfiguration = true + default: + return nil, fmt.Errorf("bug: %T is neither a runtime.Object nor a runtime.ApplyConfiguration", o) } _, isUnstructured := obj.(runtime.Unstructured) + forceDisableProtoBuf := isUnstructured || isApplyConfiguration // It's better to do creation work twice than to not let multiple // people make requests at once c.mu.RLock() - resourceByType := c.structuredResourceByType - if isUnstructured { - resourceByType = c.unstructuredResourceByType - } - r, known := resourceByType[gvk] + + cacheKey := cacheKey{gvk: gvk, forceDisableProtoBuf: forceDisableProtoBuf} + + r, known := c.resourceByType[cacheKey] + c.mu.RUnlock() if known { return r, nil } + var isList bool + if runtimeObject, ok := obj.(runtime.Object); ok && meta.IsListType(runtimeObject) { + isList = true + } + // Initialize a new Client c.mu.Lock() defer c.mu.Unlock() - r, err = c.newResource(gvk, meta.IsListType(obj), isUnstructured) + r, err = c.newResource(gvk, isList, forceDisableProtoBuf, isUnstructured) if err != nil { return nil, err } - resourceByType[gvk] = r + c.resourceByType[cacheKey] = r return r, err } // getObjMeta returns objMeta containing both type and object metadata and state. -func (c *clientRestResources) getObjMeta(obj runtime.Object) (*objMeta, error) { +func (c *clientRestResources) getObjMeta(obj any) (*objMeta, error) { r, err := c.getResource(obj) if err != nil { return nil, err } - m, err := meta.Accessor(obj) - if err != nil { - return nil, err + objMeta := &objMeta{resourceMeta: r} + + switch o := obj.(type) { + case runtime.Object: + m, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + objMeta.namespace = m.GetNamespace() + objMeta.name = m.GetName() + case applyConfiguration: + objMeta.namespace = ptr.Deref(o.GetNamespace(), "") + objMeta.name = ptr.Deref(o.GetName(), "") + default: + return nil, fmt.Errorf("object %T is neither a runtime.Object nor a runtime.ApplyConfiguration", obj) } - return &objMeta{resourceMeta: r, Object: m}, err + + return objMeta, nil } // resourceMeta stores state for a Kubernetes type. @@ -146,6 +199,6 @@ type objMeta struct { // resourceMeta contains type information for the object *resourceMeta - // Object contains meta data for the object instance - metav1.Object + namespace string + name string } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/dryrun.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/dryrun.go index bbcdd3832..fb7012200 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/dryrun.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/dryrun.go @@ -82,6 +82,10 @@ func (c *dryRunClient) Patch(ctx context.Context, obj Object, patch Patch, opts return c.client.Patch(ctx, obj, patch, append(opts, DryRunAll)...) } +func (c *dryRunClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error { + return c.client.Apply(ctx, obj, append(opts, DryRunAll)...) +} + // Get implements client.Client. func (c *dryRunClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error { return c.client.Get(ctx, key, obj, opts...) @@ -128,3 +132,7 @@ func (sw *dryRunSubResourceClient) Update(ctx context.Context, obj Object, opts func (sw *dryRunSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { return sw.client.Patch(ctx, obj, patch, append(opts, DryRunAll)...) } + +func (sw *dryRunSubResourceClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...SubResourceApplyOption) error { + return sw.client.Apply(ctx, obj, append(opts, DryRunAll)...) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldowner.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldowner.go index 07183cd19..5d9437ba9 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldowner.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldowner.go @@ -54,6 +54,10 @@ func (f *clientWithFieldManager) Patch(ctx context.Context, obj Object, patch Pa return f.c.Patch(ctx, obj, patch, append([]PatchOption{FieldOwner(f.owner)}, opts...)...) } +func (f *clientWithFieldManager) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error { + return f.c.Apply(ctx, obj, append([]ApplyOption{FieldOwner(f.owner)}, opts...)...) +} + func (f *clientWithFieldManager) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error { return f.c.Delete(ctx, obj, opts...) } @@ -104,3 +108,7 @@ func (f *subresourceClientWithFieldOwner) Update(ctx context.Context, obj Object func (f *subresourceClientWithFieldOwner) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { return f.subresourceWriter.Patch(ctx, obj, patch, append([]SubResourcePatchOption{FieldOwner(f.owner)}, opts...)...) } + +func (f *subresourceClientWithFieldOwner) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...SubResourceApplyOption) error { + return f.subresourceWriter.Apply(ctx, obj, append([]SubResourceApplyOption{FieldOwner(f.owner)}, opts...)...) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldvalidation.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldvalidation.go index 659b3d44c..b0f660854 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldvalidation.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fieldvalidation.go @@ -27,6 +27,9 @@ import ( // WithFieldValidation wraps a Client and configures field validation, by // default, for all write requests from this client. Users can override field // validation for individual write requests. +// +// This wrapper has no effect on apply requests, as they do not support a +// custom fieldValidation setting, it is always strict. func WithFieldValidation(c Client, validation FieldValidation) Client { return &clientWithFieldValidation{ validation: validation, @@ -53,6 +56,10 @@ func (c *clientWithFieldValidation) Patch(ctx context.Context, obj Object, patch return c.client.Patch(ctx, obj, patch, append([]PatchOption{c.validation}, opts...)...) } +func (c *clientWithFieldValidation) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error { + return c.client.Apply(ctx, obj, opts...) +} + func (c *clientWithFieldValidation) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error { return c.client.Delete(ctx, obj, opts...) } @@ -104,3 +111,7 @@ func (c *subresourceClientWithFieldValidation) Update(ctx context.Context, obj O func (c *subresourceClientWithFieldValidation) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error { return c.subresourceWriter.Patch(ctx, obj, patch, append([]SubResourcePatchOption{c.validation}, opts...)...) } + +func (c *subresourceClientWithFieldValidation) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...SubResourceApplyOption) error { + return c.subresourceWriter.Apply(ctx, obj, opts...) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go index 3b282fc2c..1af1f3a36 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go @@ -61,6 +61,9 @@ type Reader interface { // Writer knows how to create, delete, and update Kubernetes objects. type Writer interface { + // Apply applies the given apply configuration to the Kubernetes cluster. + Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error + // Create saves the object obj in the Kubernetes cluster. obj must be a // struct pointer so that obj can be updated with the content returned by the Server. Create(ctx context.Context, obj Object, opts ...CreateOption) error @@ -152,6 +155,9 @@ type SubResourceWriter interface { // pointer so that obj can be updated with the content returned by the // Server. Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error + + // Apply applies the given apply configurations subresource. + Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...SubResourceApplyOption) error } // SubResourceClient knows how to perform CRU operations on Kubernetes objects. diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go index 222dc7957..ebbbc4fdd 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go @@ -19,10 +19,13 @@ package client import ( "context" "fmt" + "reflect" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) // NewNamespacedClient wraps an existing client enforcing the namespace value. @@ -147,6 +150,60 @@ func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, o return n.client.Patch(ctx, obj, patch, opts...) } +func (n *namespacedClient) setNamespaceForApplyConfigIfNamespaceScoped(obj runtime.ApplyConfiguration) error { + var gvk schema.GroupVersionKind + switch o := obj.(type) { + case applyConfiguration: + var err error + gvk, err = gvkFromApplyConfiguration(o) + if err != nil { + return err + } + case *unstructuredApplyConfiguration: + gvk = o.GroupVersionKind() + default: + return fmt.Errorf("object %T is not a valid apply configuration", obj) + } + isNamespaceScoped, err := apiutil.IsGVKNamespaced(gvk, n.RESTMapper()) + if err != nil { + return fmt.Errorf("error finding the scope of the object: %w", err) + } + if isNamespaceScoped { + switch o := obj.(type) { + case applyConfiguration: + if o.GetNamespace() != nil && *o.GetNamespace() != "" && *o.GetNamespace() != n.namespace { + return fmt.Errorf("namespace %s provided for the object %s does not match the namespace %s on the client", + *o.GetNamespace(), ptr.Deref(o.GetName(), ""), n.namespace) + } + v := reflect.ValueOf(o) + withNamespace := v.MethodByName("WithNamespace") + if !withNamespace.IsValid() { + return fmt.Errorf("ApplyConfiguration %T does not have a WithNamespace method", o) + } + if tp := withNamespace.Type(); tp.NumIn() != 1 || tp.In(0).Kind() != reflect.String { + return fmt.Errorf("WithNamespace method of ApplyConfiguration %T must take a single string argument", o) + } + withNamespace.Call([]reflect.Value{reflect.ValueOf(n.namespace)}) + case *unstructuredApplyConfiguration: + if o.GetNamespace() != "" && o.GetNamespace() != n.namespace { + return fmt.Errorf("namespace %s provided for the object %s does not match the namespace %s on the client", + o.GetNamespace(), o.GetName(), n.namespace) + } + o.SetNamespace(n.namespace) + } + } + + return nil +} + +func (n *namespacedClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error { + if err := n.setNamespaceForApplyConfigIfNamespaceScoped(obj); err != nil { + return err + } + + return n.client.Apply(ctx, obj, opts...) +} + // Get implements client.Client. func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error { isNamespaceScoped, err := n.IsObjectNamespaced(obj) @@ -164,7 +221,12 @@ func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object, o // List implements client.Client. func (n *namespacedClient) List(ctx context.Context, obj ObjectList, opts ...ListOption) error { - if n.namespace != "" { + isNamespaceScoped, err := n.IsObjectNamespaced(obj) + if err != nil { + return fmt.Errorf("error finding the scope of the object: %w", err) + } + + if isNamespaceScoped && n.namespace != "" { opts = append(opts, InNamespace(n.namespace)) } return n.client.List(ctx, obj, opts...) @@ -177,7 +239,10 @@ func (n *namespacedClient) Status() SubResourceWriter { // SubResource implements client.SubResourceClient. func (n *namespacedClient) SubResource(subResource string) SubResourceClient { - return &namespacedClientSubResourceClient{client: n.client.SubResource(subResource), namespace: n.namespace, namespacedclient: n} + return &namespacedClientSubResourceClient{ + client: n.client.SubResource(subResource), + namespacedclient: n, + } } // ensure namespacedClientSubResourceClient implements client.SubResourceClient. @@ -185,8 +250,7 @@ var _ SubResourceClient = &namespacedClientSubResourceClient{} type namespacedClientSubResourceClient struct { client SubResourceClient - namespace string - namespacedclient Client + namespacedclient *namespacedClient } func (nsw *namespacedClientSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error { @@ -196,12 +260,12 @@ func (nsw *namespacedClientSubResourceClient) Get(ctx context.Context, obj, subR } objectNamespace := obj.GetNamespace() - if objectNamespace != nsw.namespace && objectNamespace != "" { - return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace) + if objectNamespace != nsw.namespacedclient.namespace && objectNamespace != "" { + return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespacedclient.namespace) } if isNamespaceScoped && objectNamespace == "" { - obj.SetNamespace(nsw.namespace) + obj.SetNamespace(nsw.namespacedclient.namespace) } return nsw.client.Get(ctx, obj, subResource, opts...) @@ -214,12 +278,12 @@ func (nsw *namespacedClientSubResourceClient) Create(ctx context.Context, obj, s } objectNamespace := obj.GetNamespace() - if objectNamespace != nsw.namespace && objectNamespace != "" { - return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace) + if objectNamespace != nsw.namespacedclient.namespace && objectNamespace != "" { + return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespacedclient.namespace) } if isNamespaceScoped && objectNamespace == "" { - obj.SetNamespace(nsw.namespace) + obj.SetNamespace(nsw.namespacedclient.namespace) } return nsw.client.Create(ctx, obj, subResource, opts...) @@ -233,12 +297,12 @@ func (nsw *namespacedClientSubResourceClient) Update(ctx context.Context, obj Ob } objectNamespace := obj.GetNamespace() - if objectNamespace != nsw.namespace && objectNamespace != "" { - return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace) + if objectNamespace != nsw.namespacedclient.namespace && objectNamespace != "" { + return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespacedclient.namespace) } if isNamespaceScoped && objectNamespace == "" { - obj.SetNamespace(nsw.namespace) + obj.SetNamespace(nsw.namespacedclient.namespace) } return nsw.client.Update(ctx, obj, opts...) } @@ -251,12 +315,19 @@ func (nsw *namespacedClientSubResourceClient) Patch(ctx context.Context, obj Obj } objectNamespace := obj.GetNamespace() - if objectNamespace != nsw.namespace && objectNamespace != "" { - return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace) + if objectNamespace != nsw.namespacedclient.namespace && objectNamespace != "" { + return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespacedclient.namespace) } if isNamespaceScoped && objectNamespace == "" { - obj.SetNamespace(nsw.namespace) + obj.SetNamespace(nsw.namespacedclient.namespace) } return nsw.client.Patch(ctx, obj, patch, opts...) } + +func (nsw *namespacedClientSubResourceClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...SubResourceApplyOption) error { + if err := nsw.namespacedclient.setNamespaceForApplyConfigIfNamespaceScoped(obj); err != nil { + return err + } + return nsw.client.Apply(ctx, obj, opts...) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go index db50ed8fe..a6b921171 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" + "k8s.io/utils/ptr" ) // {{{ "Functional" Option Interfaces @@ -61,6 +62,12 @@ type PatchOption interface { ApplyToPatch(*PatchOptions) } +// ApplyOption is some configuration that modifies options for an apply request. +type ApplyOption interface { + // ApplyToApply applies this configuration to the given apply options. + ApplyToApply(*ApplyOptions) +} + // DeleteAllOfOption is some configuration that modifies options for a delete request. type DeleteAllOfOption interface { // ApplyToDeleteAllOf applies this configuration to the given deletecollection options. @@ -90,6 +97,12 @@ type SubResourcePatchOption interface { ApplyToSubResourcePatch(*SubResourcePatchOptions) } +// SubResourceApplyOption configures a subresource apply request. +type SubResourceApplyOption interface { + // ApplyToSubResourceApply applies the configuration on the given patch options. + ApplyToSubResourceApply(*SubResourceApplyOptions) +} + // }}} // {{{ Multi-Type Options @@ -115,7 +128,12 @@ func (dryRunAll) ApplyToPatch(opts *PatchOptions) { opts.DryRun = []string{metav1.DryRunAll} } -// ApplyToPatch applies this configuration to the given delete options. +// ApplyToApply applies this configuration to the given apply options. +func (dryRunAll) ApplyToApply(opts *ApplyOptions) { + opts.DryRun = []string{metav1.DryRunAll} +} + +// ApplyToDelete applies this configuration to the given delete options. func (dryRunAll) ApplyToDelete(opts *DeleteOptions) { opts.DryRun = []string{metav1.DryRunAll} } @@ -136,6 +154,10 @@ func (dryRunAll) ApplyToSubResourcePatch(opts *SubResourcePatchOptions) { opts.DryRun = []string{metav1.DryRunAll} } +func (dryRunAll) ApplyToSubResourceApply(opts *SubResourceApplyOptions) { + opts.DryRun = []string{metav1.DryRunAll} +} + // FieldOwner set the field manager name for the given server-side apply patch. type FieldOwner string @@ -154,6 +176,11 @@ func (f FieldOwner) ApplyToUpdate(opts *UpdateOptions) { opts.FieldManager = string(f) } +// ApplyToApply applies this configuration to the given apply options. +func (f FieldOwner) ApplyToApply(opts *ApplyOptions) { + opts.FieldManager = string(f) +} + // ApplyToSubResourcePatch applies this configuration to the given patch options. func (f FieldOwner) ApplyToSubResourcePatch(opts *SubResourcePatchOptions) { opts.FieldManager = string(f) @@ -169,6 +196,11 @@ func (f FieldOwner) ApplyToSubResourceUpdate(opts *SubResourceUpdateOptions) { opts.FieldManager = string(f) } +// ApplyToSubResourceApply applies this configuration to the given apply options. +func (f FieldOwner) ApplyToSubResourceApply(opts *SubResourceApplyOptions) { + opts.FieldManager = string(f) +} + // FieldValidation configures field validation for the given requests. type FieldValidation string @@ -431,6 +463,12 @@ type GetOptions struct { // Raw represents raw GetOptions, as passed to the API server. Note // that these may not be respected by all implementations of interface. Raw *metav1.GetOptions + + // UnsafeDisableDeepCopy indicates not to deep copy objects during get object. + // Be very careful with this, when enabled you must DeepCopy any object before mutating it, + // otherwise you will mutate the object in the cache. + // +optional + UnsafeDisableDeepCopy *bool } var _ GetOption = &GetOptions{} @@ -440,6 +478,9 @@ func (o *GetOptions) ApplyToGet(lo *GetOptions) { if o.Raw != nil { lo.Raw = o.Raw } + if o.UnsafeDisableDeepCopy != nil { + lo.UnsafeDisableDeepCopy = o.UnsafeDisableDeepCopy + } } // AsGetOptions returns these options as a flattened metav1.GetOptions. @@ -618,6 +659,9 @@ type MatchingLabelsSelector struct { // ApplyToList applies this configuration to the given list options. func (m MatchingLabelsSelector) ApplyToList(opts *ListOptions) { + if m.Selector == nil { + m.Selector = labels.Nothing() + } opts.LabelSelector = m } @@ -651,6 +695,9 @@ type MatchingFieldsSelector struct { // ApplyToList applies this configuration to the given list options. func (m MatchingFieldsSelector) ApplyToList(opts *ListOptions) { + if m.Selector == nil { + m.Selector = fields.Nothing() + } opts.FieldSelector = m } @@ -692,15 +739,14 @@ func (l Limit) ApplyToList(opts *ListOptions) { // otherwise you will mutate the object in the cache. type UnsafeDisableDeepCopyOption bool +// ApplyToGet applies this configuration to the given an Get options. +func (d UnsafeDisableDeepCopyOption) ApplyToGet(opts *GetOptions) { + opts.UnsafeDisableDeepCopy = ptr.To(bool(d)) +} + // ApplyToList applies this configuration to the given an List options. func (d UnsafeDisableDeepCopyOption) ApplyToList(opts *ListOptions) { - definitelyTrue := true - definitelyFalse := false - if d { - opts.UnsafeDisableDeepCopy = &definitelyTrue - } else { - opts.UnsafeDisableDeepCopy = &definitelyFalse - } + opts.UnsafeDisableDeepCopy = ptr.To(bool(d)) } // UnsafeDisableDeepCopy indicates not to deep copy objects during list objects. @@ -863,10 +909,18 @@ func (o *PatchOptions) AsPatchOptions() *metav1.PatchOptions { o.Raw = &metav1.PatchOptions{} } - o.Raw.DryRun = o.DryRun - o.Raw.Force = o.Force - o.Raw.FieldManager = o.FieldManager - o.Raw.FieldValidation = o.FieldValidation + if o.DryRun != nil { + o.Raw.DryRun = o.DryRun + } + if o.Force != nil { + o.Raw.Force = o.Force + } + if o.FieldManager != "" { + o.Raw.FieldManager = o.FieldManager + } + if o.FieldValidation != "" { + o.Raw.FieldValidation = o.FieldValidation + } return o.Raw } @@ -899,13 +953,19 @@ var ForceOwnership = forceOwnership{} type forceOwnership struct{} func (forceOwnership) ApplyToPatch(opts *PatchOptions) { - definitelyTrue := true - opts.Force = &definitelyTrue + opts.Force = ptr.To(true) } func (forceOwnership) ApplyToSubResourcePatch(opts *SubResourcePatchOptions) { - definitelyTrue := true - opts.Force = &definitelyTrue + opts.Force = ptr.To(true) +} + +func (forceOwnership) ApplyToApply(opts *ApplyOptions) { + opts.Force = ptr.To(true) +} + +func (forceOwnership) ApplyToSubResourceApply(opts *SubResourceApplyOptions) { + opts.Force = ptr.To(true) } // }}} @@ -939,3 +999,57 @@ func (o *DeleteAllOfOptions) ApplyToDeleteAllOf(do *DeleteAllOfOptions) { } // }}} + +// ApplyOptions are the options for an apply request. +type ApplyOptions struct { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + DryRun []string + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. + Force *bool + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. This + // field is required. + // + // +required + FieldManager string +} + +// ApplyOptions applies the given opts onto the ApplyOptions +func (o *ApplyOptions) ApplyOptions(opts []ApplyOption) *ApplyOptions { + for _, opt := range opts { + opt.ApplyToApply(o) + } + return o +} + +// ApplyToApply applies the given opts onto the ApplyOptions +func (o *ApplyOptions) ApplyToApply(opts *ApplyOptions) { + if o.DryRun != nil { + opts.DryRun = o.DryRun + } + if o.Force != nil { + opts.Force = o.Force + } + + if o.FieldManager != "" { + opts.FieldManager = o.FieldManager + } +} + +// AsPatchOptions constructs patch options from the given ApplyOptions +func (o *ApplyOptions) AsPatchOptions() *metav1.PatchOptions { + return &metav1.PatchOptions{ + DryRun: o.DryRun, + Force: o.Force, + FieldManager: o.FieldManager, + } +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/patch.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/patch.go index 11d608388..3d914eea2 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/patch.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/patch.go @@ -27,6 +27,8 @@ import ( var ( // Apply uses server-side apply to patch the given object. + // + // Deprecated: Use client.Client.Apply() and client.Client.SubResource("subrsource").Apply() instead. Apply Patch = applyPatch{} // Merge uses the raw object as a merge patch, without modifications. @@ -86,7 +88,7 @@ type MergeFromOptions struct { type mergeFromPatch struct { patchType types.PatchType - createPatch func(originalJSON, modifiedJSON []byte, dataStruct interface{}) ([]byte, error) + createPatch func(originalJSON, modifiedJSON []byte, dataStruct any) ([]byte, error) from Object opts MergeFromOptions } @@ -132,11 +134,11 @@ func (s *mergeFromPatch) Data(obj Object) ([]byte, error) { return data, nil } -func createMergePatch(originalJSON, modifiedJSON []byte, _ interface{}) ([]byte, error) { +func createMergePatch(originalJSON, modifiedJSON []byte, _ any) ([]byte, error) { return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON) } -func createStrategicMergePatch(originalJSON, modifiedJSON []byte, dataStruct interface{}) ([]byte, error) { +func createStrategicMergePatch(originalJSON, modifiedJSON []byte, dataStruct any) ([]byte, error) { return strategicpatch.CreateTwoWayMergePatch(originalJSON, modifiedJSON, dataStruct) } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go index 92afd9a9c..66ae2e4a5 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go @@ -18,8 +18,10 @@ package client import ( "context" + "fmt" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/util/apply" ) var _ Reader = &typedClient{} @@ -41,7 +43,7 @@ func (c *typedClient) Create(ctx context.Context, obj Object, opts ...CreateOpti createOpts.ApplyOptions(opts) return o.Post(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). Body(obj). VersionedParams(createOpts.AsCreateOptions(), c.paramCodec). @@ -60,9 +62,9 @@ func (c *typedClient) Update(ctx context.Context, obj Object, opts ...UpdateOpti updateOpts.ApplyOptions(opts) return o.Put(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). Body(obj). VersionedParams(updateOpts.AsUpdateOptions(), c.paramCodec). Do(ctx). @@ -80,9 +82,9 @@ func (c *typedClient) Delete(ctx context.Context, obj Object, opts ...DeleteOpti deleteOpts.ApplyOptions(opts) return o.Delete(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). Body(deleteOpts.AsDeleteOptions()). Do(ctx). Error() @@ -123,15 +125,40 @@ func (c *typedClient) Patch(ctx context.Context, obj Object, patch Patch, opts . patchOpts.ApplyOptions(opts) return o.Patch(patch.Type()). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). VersionedParams(patchOpts.AsPatchOptions(), c.paramCodec). Body(data). Do(ctx). Into(obj) } +func (c *typedClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error { + o, err := c.resources.getObjMeta(obj) + if err != nil { + return err + } + req, err := apply.NewRequest(o, obj) + if err != nil { + return fmt.Errorf("failed to create apply request: %w", err) + } + applyOpts := &ApplyOptions{} + applyOpts.ApplyOptions(opts) + + return req. + NamespaceIfScoped(o.namespace, o.isNamespaced()). + Resource(o.resource()). + Name(o.name). + VersionedParams(applyOpts.AsPatchOptions(), c.paramCodec). + Do(ctx). + // This is hacky, it is required because `Into` takes a `runtime.Object` and + // that is not implemented by the ApplyConfigurations. The generated clients + // don't have this problem because they deserialize into the api type, not the + // apply configuration: https://github.com/kubernetes/kubernetes/blob/22f5e01a37c0bc6a5f494dec14dd4e3688ee1d55/staging/src/k8s.io/client-go/gentype/type.go#L296-L317 + Into(runtimeObjectFromApplyConfiguration(obj)) +} + // Get implements client.Client. func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error { r, err := c.resources.getResource(obj) @@ -179,9 +206,9 @@ func (c *typedClient) GetSubResource(ctx context.Context, obj, subResourceObj Ob getOpts.ApplyOptions(opts) return o.Get(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). VersionedParams(getOpts.AsGetOptions(), c.paramCodec). Do(ctx). @@ -202,9 +229,9 @@ func (c *typedClient) CreateSubResource(ctx context.Context, obj Object, subReso createOpts.ApplyOptions(opts) return o.Post(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). Body(subResourceObj). VersionedParams(createOpts.AsCreateOptions(), c.paramCodec). @@ -237,9 +264,9 @@ func (c *typedClient) UpdateSubResource(ctx context.Context, obj Object, subReso } return o.Put(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). Body(body). VersionedParams(updateOpts.AsUpdateOptions(), c.paramCodec). @@ -268,12 +295,45 @@ func (c *typedClient) PatchSubResource(ctx context.Context, obj Object, subResou } return o.Patch(patch.Type()). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). Body(data). VersionedParams(patchOpts.AsPatchOptions(), c.paramCodec). Do(ctx). Into(body) } + +func (c *typedClient) ApplySubResource(ctx context.Context, obj runtime.ApplyConfiguration, subResource string, opts ...SubResourceApplyOption) error { + o, err := c.resources.getObjMeta(obj) + if err != nil { + return err + } + + applyOpts := &SubResourceApplyOptions{} + applyOpts.ApplyOpts(opts) + + body := obj + if applyOpts.SubResourceBody != nil { + body = applyOpts.SubResourceBody + } + + req, err := apply.NewRequest(o, body) + if err != nil { + return fmt.Errorf("failed to create apply request: %w", err) + } + + return req. + NamespaceIfScoped(o.namespace, o.isNamespaced()). + Resource(o.resource()). + Name(o.name). + SubResource(subResource). + VersionedParams(applyOpts.AsPatchOptions(), c.paramCodec). + Do(ctx). + // This is hacky, it is required because `Into` takes a `runtime.Object` and + // that is not implemented by the ApplyConfigurations. The generated clients + // don't have this problem because they deserialize into the api type, not the + // apply configuration: https://github.com/kubernetes/kubernetes/blob/22f5e01a37c0bc6a5f494dec14dd4e3688ee1d55/staging/src/k8s.io/client-go/gentype/type.go#L296-L317 + Into(runtimeObjectFromApplyConfiguration(obj)) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go index 0d9695178..d2ea6d7a3 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go @@ -22,6 +22,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/util/apply" ) var _ Reader = &unstructuredClient{} @@ -50,7 +51,7 @@ func (uc *unstructuredClient) Create(ctx context.Context, obj Object, opts ...Cr createOpts.ApplyOptions(opts) result := o.Post(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). Body(obj). VersionedParams(createOpts.AsCreateOptions(), uc.paramCodec). @@ -79,9 +80,9 @@ func (uc *unstructuredClient) Update(ctx context.Context, obj Object, opts ...Up updateOpts.ApplyOptions(opts) result := o.Put(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). Body(obj). VersionedParams(updateOpts.AsUpdateOptions(), uc.paramCodec). Do(ctx). @@ -106,9 +107,9 @@ func (uc *unstructuredClient) Delete(ctx context.Context, obj Object, opts ...De deleteOpts.ApplyOptions(opts) return o.Delete(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). Body(deleteOpts.AsDeleteOptions()). Do(ctx). Error() @@ -157,15 +158,41 @@ func (uc *unstructuredClient) Patch(ctx context.Context, obj Object, patch Patch patchOpts.ApplyOptions(opts) return o.Patch(patch.Type()). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). VersionedParams(patchOpts.AsPatchOptions(), uc.paramCodec). Body(data). Do(ctx). Into(obj) } +func (uc *unstructuredClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error { + unstructuredApplyConfig, ok := obj.(*unstructuredApplyConfiguration) + if !ok { + return fmt.Errorf("bug: unstructured client got an applyconfiguration that was not %T but %T", &unstructuredApplyConfiguration{}, obj) + } + o, err := uc.resources.getObjMeta(unstructuredApplyConfig.Unstructured) + if err != nil { + return err + } + + req, err := apply.NewRequest(o, obj) + if err != nil { + return fmt.Errorf("failed to create apply request: %w", err) + } + applyOpts := &ApplyOptions{} + applyOpts.ApplyOptions(opts) + + return req. + NamespaceIfScoped(o.namespace, o.isNamespaced()). + Resource(o.resource()). + Name(o.name). + VersionedParams(applyOpts.AsPatchOptions(), uc.paramCodec). + Do(ctx). + Into(unstructuredApplyConfig.Unstructured) +} + // Get implements client.Client. func (uc *unstructuredClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error { u, ok := obj.(runtime.Unstructured) @@ -244,9 +271,9 @@ func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResour getOpts.ApplyOptions(opts) return o.Get(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). VersionedParams(getOpts.AsGetOptions(), uc.paramCodec). Do(ctx). @@ -275,9 +302,9 @@ func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subRes createOpts.ApplyOptions(opts) return o.Post(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). Body(subResourceObj). VersionedParams(createOpts.AsCreateOptions(), uc.paramCodec). @@ -310,9 +337,9 @@ func (uc *unstructuredClient) UpdateSubResource(ctx context.Context, obj Object, } return o.Put(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). Body(body). VersionedParams(updateOpts.AsUpdateOptions(), uc.paramCodec). @@ -347,9 +374,9 @@ func (uc *unstructuredClient) PatchSubResource(ctx context.Context, obj Object, } result := o.Patch(patch.Type()). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + NamespaceIfScoped(o.namespace, o.isNamespaced()). Resource(o.resource()). - Name(o.GetName()). + Name(o.name). SubResource(subResource). Body(data). VersionedParams(patchOpts.AsPatchOptions(), uc.paramCodec). @@ -359,3 +386,35 @@ func (uc *unstructuredClient) PatchSubResource(ctx context.Context, obj Object, u.GetObjectKind().SetGroupVersionKind(gvk) return result } + +func (uc *unstructuredClient) ApplySubResource(ctx context.Context, obj runtime.ApplyConfiguration, subResource string, opts ...SubResourceApplyOption) error { + unstructuredApplyConfig, ok := obj.(*unstructuredApplyConfiguration) + if !ok { + return fmt.Errorf("bug: unstructured client got an applyconfiguration that was not %T but %T", &unstructuredApplyConfiguration{}, obj) + } + o, err := uc.resources.getObjMeta(unstructuredApplyConfig.Unstructured) + if err != nil { + return err + } + + applyOpts := &SubResourceApplyOptions{} + applyOpts.ApplyOpts(opts) + + body := obj + if applyOpts.SubResourceBody != nil { + body = applyOpts.SubResourceBody + } + req, err := apply.NewRequest(o, body) + if err != nil { + return fmt.Errorf("failed to create apply request: %w", err) + } + + return req. + NamespaceIfScoped(o.namespace, o.isNamespaced()). + Resource(o.resource()). + Name(o.name). + SubResource(subResource). + VersionedParams(applyOpts.AsPatchOptions(), uc.paramCodec). + Do(ctx). + Into(unstructuredApplyConfig.Unstructured) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go index ba3f931e4..0f12b934e 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "reflect" + "slices" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -278,7 +279,7 @@ func referSameObject(a, b metav1.OwnerReference) bool { return aGV.Group == bGV.Group && a.Kind == b.Kind && a.Name == b.Name } -// OperationResult is the action result of a CreateOrUpdate call. +// OperationResult is the action result of a CreateOrUpdate or CreateOrPatch call. type OperationResult string const ( // They should complete the sentence "Deployment default/foo has been ..." @@ -294,13 +295,26 @@ const ( // They should complete the sentence "Deployment default/foo has been .. OperationResultUpdatedStatusOnly OperationResult = "updatedStatusOnly" ) -// CreateOrUpdate creates or updates the given object in the Kubernetes -// cluster. The object's desired state must be reconciled with the existing -// state inside the passed in callback MutateFn. +// CreateOrUpdate attempts to fetch the given object from the Kubernetes cluster. +// If the object didn't exist, MutateFn will be called, and it will be created. +// If the object did exist, MutateFn will be called, and if it changed the +// object, it will be updated. +// Otherwise, it will be left unchanged. +// The executed operation (and an error) will be returned. // -// The MutateFn is called regardless of creating or updating an object. +// WARNING: If the MutateFn resets a value on obj that has a default value, +// CreateOrUpdate will *always* perform an update. This is because when the +// object is fetched from the API server, the value will have taken on the +// default value, and the check for equality will fail. For example, Deployments +// must have a Replicas value set. If the MutateFn sets a Deployment's Replicas +// to nil, then it will never match with the object returned from the API +// server, which defaults the value to 1. // -// It returns the executed operation and an error. +// WARNING: CreateOrUpdate assumes that no values have been set on obj aside +// from the Name/Namespace. Values other than Name and Namespace that existed on +// obj may be overwritten by the corresponding values in the object returned +// from the Kubernetes API server. When this happens, the Update will not work +// as expected. // // Note: changes made by MutateFn to any sub-resource (status...), will be // discarded. @@ -310,9 +324,12 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f M if !apierrors.IsNotFound(err) { return OperationResultNone, err } - if err := mutate(f, key, obj); err != nil { - return OperationResultNone, err + if f != nil { + if err := mutate(f, key, obj); err != nil { + return OperationResultNone, err + } } + if err := c.Create(ctx, obj); err != nil { return OperationResultNone, err } @@ -320,8 +337,10 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f M } existing := obj.DeepCopyObject() - if err := mutate(f, key, obj); err != nil { - return OperationResultNone, err + if f != nil { + if err := mutate(f, key, obj); err != nil { + return OperationResultNone, err + } } if equality.Semantic.DeepEqual(existing, obj) { @@ -334,13 +353,26 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f M return OperationResultUpdated, nil } -// CreateOrPatch creates or patches the given object in the Kubernetes -// cluster. The object's desired state must be reconciled with the before -// state inside the passed in callback MutateFn. +// CreateOrPatch attempts to fetch the given object from the Kubernetes cluster. +// If the object didn't exist, MutateFn will be called, and it will be created. +// If the object did exist, MutateFn will be called, and if it changed the +// object, it will be patched. +// Otherwise, it will be left unchanged. +// The executed operation (and an error) will be returned. // -// The MutateFn is called regardless of creating or updating an object. +// WARNING: If the MutateFn resets a value on obj that has a default value, +// CreateOrPatch will *always* perform a patch. This is because when the +// object is fetched from the API server, the value will have taken on the +// default value, and the check for equality will fail. +// For example, Deployments must have a Replicas value set. If the MutateFn sets +// a Deployment's Replicas to nil, then it will never match with the object +// returned from the API server, which defaults the value to 1. // -// It returns the executed operation and an error. +// WARNING: CreateOrPatch assumes that no values have been set on obj aside +// from the Name/Namespace. Values other than Name and Namespace that existed on +// obj may be overwritten by the corresponding values in the object returned +// from the Kubernetes API server. When this happens, the Patch will not work +// as expected. // // Note: changes to any sub-resource other than status will be ignored. // Changes to the status sub-resource will only be applied if the object @@ -470,10 +502,8 @@ type MutateFn func() error // It returns an indication of whether it updated the object's list of finalizers. func AddFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) { f := o.GetFinalizers() - for _, e := range f { - if e == finalizer { - return false - } + if slices.Contains(f, finalizer) { + return false } o.SetFinalizers(append(f, finalizer)) return true @@ -486,7 +516,7 @@ func RemoveFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) length := len(f) index := 0 - for i := 0; i < length; i++ { + for i := range length { if f[i] == finalizer { continue } @@ -500,10 +530,5 @@ func RemoveFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) // ContainsFinalizer checks an Object that the provided finalizer is present. func ContainsFinalizer(o client.Object, finalizer string) bool { f := o.GetFinalizers() - for _, e := range f { - if e == finalizer { - return true - } - } - return false + return slices.Contains(f, finalizer) } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/log/deleg.go b/vendor/sigs.k8s.io/controller-runtime/pkg/log/deleg.go index 6eb551d3b..948330b01 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/log/deleg.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/log/deleg.go @@ -30,7 +30,7 @@ type loggerPromise struct { promisesLock sync.Mutex name *string - tags []interface{} + tags []any } func (p *loggerPromise) WithName(l *delegatingLogSink, name string) *loggerPromise { @@ -47,7 +47,7 @@ func (p *loggerPromise) WithName(l *delegatingLogSink, name string) *loggerPromi } // WithValues provides a new Logger with the tags appended. -func (p *loggerPromise) WithValues(l *delegatingLogSink, tags ...interface{}) *loggerPromise { +func (p *loggerPromise) WithValues(l *delegatingLogSink, tags ...any) *loggerPromise { res := &loggerPromise{ logger: l, tags: tags, @@ -120,7 +120,7 @@ func (l *delegatingLogSink) Enabled(level int) bool { // the log line. The key/value pairs can then be used to add additional // variable information. The key/value pairs should alternate string // keys and arbitrary values. -func (l *delegatingLogSink) Info(level int, msg string, keysAndValues ...interface{}) { +func (l *delegatingLogSink) Info(level int, msg string, keysAndValues ...any) { eventuallyFulfillRoot() l.lock.RLock() defer l.lock.RUnlock() @@ -135,7 +135,7 @@ func (l *delegatingLogSink) Info(level int, msg string, keysAndValues ...interfa // The msg field should be used to add context to any underlying error, // while the err field should be used to attach the actual error that // triggered this log line, if present. -func (l *delegatingLogSink) Error(err error, msg string, keysAndValues ...interface{}) { +func (l *delegatingLogSink) Error(err error, msg string, keysAndValues ...any) { eventuallyFulfillRoot() l.lock.RLock() defer l.lock.RUnlock() @@ -164,7 +164,7 @@ func (l *delegatingLogSink) WithName(name string) logr.LogSink { } // WithValues provides a new Logger with the tags appended. -func (l *delegatingLogSink) WithValues(tags ...interface{}) logr.LogSink { +func (l *delegatingLogSink) WithValues(tags ...any) logr.LogSink { eventuallyFulfillRoot() l.lock.RLock() defer l.lock.RUnlock() diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/log/log.go b/vendor/sigs.k8s.io/controller-runtime/pkg/log/log.go index ade21d6fb..48a4d490d 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/log/log.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/log/log.go @@ -88,7 +88,7 @@ var ( ) // FromContext returns a logger with predefined values from a context.Context. -func FromContext(ctx context.Context, keysAndValues ...interface{}) logr.Logger { +func FromContext(ctx context.Context, keysAndValues ...any) logr.Logger { log := Log if ctx != nil { if logger, err := logr.FromContext(ctx); err == nil { diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/log/null.go b/vendor/sigs.k8s.io/controller-runtime/pkg/log/null.go index f3e81074f..f8dd84ca6 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/log/null.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/log/null.go @@ -34,7 +34,7 @@ func (log NullLogSink) Init(logr.RuntimeInfo) { } // Info implements logr.InfoLogger. -func (NullLogSink) Info(_ int, _ string, _ ...interface{}) { +func (NullLogSink) Info(_ int, _ string, _ ...any) { // Do nothing. } @@ -44,7 +44,7 @@ func (NullLogSink) Enabled(level int) bool { } // Error implements logr.Logger. -func (NullLogSink) Error(_ error, _ string, _ ...interface{}) { +func (NullLogSink) Error(_ error, _ string, _ ...any) { // Do nothing. } @@ -54,6 +54,6 @@ func (log NullLogSink) WithName(_ string) logr.LogSink { } // WithValues implements logr.Logger. -func (log NullLogSink) WithValues(_ ...interface{}) logr.LogSink { +func (log NullLogSink) WithValues(_ ...any) logr.LogSink { return log } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/log/warning_handler.go b/vendor/sigs.k8s.io/controller-runtime/pkg/log/warning_handler.go index e9522632d..413b56d2e 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/log/warning_handler.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/log/warning_handler.go @@ -17,13 +17,12 @@ limitations under the License. package log import ( + "context" "sync" - - "github.com/go-logr/logr" ) // KubeAPIWarningLoggerOptions controls the behavior -// of a rest.WarningHandler constructed using NewKubeAPIWarningLogger(). +// of a rest.WarningHandlerWithContext constructed using NewKubeAPIWarningLogger(). type KubeAPIWarningLoggerOptions struct { // Deduplicate indicates a given warning message should only be written once. // Setting this to true in a long-running process handling many warnings can @@ -33,10 +32,8 @@ type KubeAPIWarningLoggerOptions struct { // KubeAPIWarningLogger is a wrapper around // a provided logr.Logger that implements the -// rest.WarningHandler interface. +// rest.WarningHandlerWithContext interface. type KubeAPIWarningLogger struct { - // logger is used to log responses with the warning header - logger logr.Logger // opts contain options controlling warning output opts KubeAPIWarningLoggerOptions // writtenLock gurads written @@ -46,9 +43,11 @@ type KubeAPIWarningLogger struct { written map[string]struct{} } -// HandleWarningHeader handles logging for responses from API server that are -// warnings with code being 299 and uses a logr.Logger for its logging purposes. -func (l *KubeAPIWarningLogger) HandleWarningHeader(code int, agent string, message string) { +// HandleWarningHeaderWithContext handles logging for responses from API server that are +// warnings with code being 299 and uses a logr.Logger from context for its logging purposes. +func (l *KubeAPIWarningLogger) HandleWarningHeaderWithContext(ctx context.Context, code int, _ string, message string) { + log := FromContext(ctx) + if code != 299 || len(message) == 0 { return } @@ -62,13 +61,13 @@ func (l *KubeAPIWarningLogger) HandleWarningHeader(code int, agent string, messa } l.written[message] = struct{}{} } - l.logger.Info(message) + log.Info(message) } -// NewKubeAPIWarningLogger returns an implementation of rest.WarningHandler that logs warnings -// with code = 299 to the provided logr.Logger. -func NewKubeAPIWarningLogger(l logr.Logger, opts KubeAPIWarningLoggerOptions) *KubeAPIWarningLogger { - h := &KubeAPIWarningLogger{logger: l, opts: opts} +// NewKubeAPIWarningLogger returns an implementation of rest.WarningHandlerWithContext that logs warnings +// with code = 299 to the logger passed into HandleWarningHeaderWithContext via the context. +func NewKubeAPIWarningLogger(opts KubeAPIWarningLoggerOptions) *KubeAPIWarningLogger { + h := &KubeAPIWarningLogger{opts: opts} if opts.Deduplicate { h.written = map[string]struct{}{} } diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go b/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go index 5e57f80fb..b12bcb7cc 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/conf/config.go @@ -23,6 +23,7 @@ import ( "os" "os/user" "path" + "path/filepath" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -97,7 +98,15 @@ func ResolveKubeConfigFile() string { // if KUBECONFIG env is defined then use that kubeConfigPath = os.Getenv(clientcmd.RecommendedConfigPathEnvVar) if kubeConfigPath != "" { - return kubeConfigPath + // handle the variable as a path list, similar to client-go. + filePaths := filepath.SplitList(kubeConfigPath) + for _, filename := range filePaths { + if _, err := os.Stat(filename); err == nil { + return filename + } + } + // return the last path in the list if none exist yet. + return filePaths[len(filePaths)-1] } var ( diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go b/vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go index 6f558d1d0..adf9008e1 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/decoder/decoder.go @@ -27,6 +27,7 @@ import ( "os" "strings" + "github.com/vladimirvivien/gexe/http" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -83,7 +84,7 @@ func DecodeEachFile(ctx context.Context, fsys fs.FS, pattern string, handlerFn H return nil } -// DecodeAllFiles resolves files at the filesystem matching the pattern, decoding JSON or YAML files. Supports multi-document files. +// DecodeAllFiles resolves files at the filesystem matching the pattern, decoding JSON or YAML files. Supports multi-document files. // Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind. // Options may be provided to configure the behavior of the decoder. func DecodeAllFiles(ctx context.Context, fsys fs.FS, pattern string, options ...DecodeOption) ([]k8s.Object, error) { @@ -140,7 +141,7 @@ func DecodeEach(ctx context.Context, manifest io.Reader, handlerFn HandlerFunc, return nil } -// DecodeAll is a stream of documents of any Kind using either the innate typing of the scheme. +// DecodeAll is a stream of documents of any Kind using either the innate typing of the scheme. // Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind. // Options may be provided to configure the behavior of the decoder. func DecodeAll(ctx context.Context, manifest io.Reader, options ...DecodeOption) ([]k8s.Object, error) { @@ -217,6 +218,18 @@ func DecodeFile(fsys fs.FS, manifestPath string, obj k8s.Object, options ...Deco return Decode(f, obj, options...) } +// DecodeURL decodes a document from the URL of any Kind using either the innate typing of the scheme. +// Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind. +func DecodeURL(ctx context.Context, url string, handlerFn HandlerFunc, options ...DecodeOption) error { + resp := http.Get(url).Do() + if resp.Err() != nil { + return resp.Err() + } + defer resp.Body().Close() + + return DecodeEach(ctx, resp.Body(), handlerFn, options...) +} + // DecodeString decodes a single-document YAML or JSON string into the provided object. Patches are applied // after decoding to the object to update the loaded resource. func DecodeString(rawManifest string, obj k8s.Object, options ...DecodeOption) error { diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go b/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go index f0d3e5667..b80db3f5c 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/k8s/resources/resources.go @@ -20,8 +20,10 @@ import ( "bytes" "context" "errors" + "fmt" "time" + appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -159,7 +161,7 @@ func (r *Resources) Delete(ctx context.Context, obj k8s.Object, opts ...DeleteOp } func WithGracePeriod(gpt time.Duration) DeleteOption { - t := gpt.Milliseconds() + t := int64(gpt.Seconds()) return func(do *metav1.DeleteOptions) { do.GracePeriodSeconds = &t } } @@ -209,7 +211,7 @@ func WithFieldSelector(sel string) ListOption { } func WithTimeout(to time.Duration) ListOption { - t := to.Milliseconds() + t := int64(to.Seconds()) return func(lo *metav1.ListOptions) { lo.TimeoutSeconds = &t } } @@ -329,6 +331,125 @@ func (r *Resources) ExecInPod(ctx context.Context, namespaceName, podName, conta return nil } +type matcher[T any] func([]T) (T, error) + +type predicate[T any] func(T) bool + +func match[T any](pred predicate[T], err error) matcher[T] { + return func(values []T) (T, error) { + for _, value := range values { + if pred(value) { + return value, nil + } + } + + var notFound T + return notFound, err + } +} + +func matchByIndex[T any](idx int, err error) matcher[T] { + return func(items []T) (T, error) { + if idx >= len(items) { + var notFound T + return notFound, fmt.Errorf("%w: index %d is out of range with length %d", err, idx, len(items)) + } + + return items[idx], nil + } +} + +var ( + errPodNotFound = errors.New("pod not found") + errContainerNotFound = errors.New("container not found") +) + +type deploymentOptions struct { + podMatcher matcher[v1.Pod] + containerMatcher matcher[v1.Container] +} + +// DeploymentOption extends the default behavior of [ExecInDeployment]. +type DeploymentOption func(*deploymentOptions) + +// WithDeploymentPod selects the pod that matches the given condition. +func WithDeploymentPod(pred predicate[v1.Pod]) DeploymentOption { + return func(opts *deploymentOptions) { + opts.podMatcher = match(pred, errPodNotFound) + } +} + +// WithDeploymentPodIndex selects the pod at the given index. +func WithDeploymentPodIndex(idx int) DeploymentOption { + return func(opts *deploymentOptions) { + opts.podMatcher = matchByIndex[v1.Pod](idx, errPodNotFound) + } +} + +// WithDeploymentContainer selects the container that matches the given condition. +func WithDeploymentContainer(pred predicate[v1.Container]) DeploymentOption { + return func(opts *deploymentOptions) { + opts.containerMatcher = match(pred, errContainerNotFound) + } +} + +// WithDeploymentContainerIndex selects the container at the given index. +func WithDeploymentContainerIndex(idx int) DeploymentOption { + return func(opts *deploymentOptions) { + opts.containerMatcher = matchByIndex[v1.Container](idx, errContainerNotFound) + } +} + +// WithDeploymentContainerName selects the container with the given name. +func WithDeploymentContainerName(name string) DeploymentOption { + return func(opts *deploymentOptions) { + opts.containerMatcher = match( + func(c v1.Container) bool { return c.Name == name }, + fmt.Errorf("%w: name %q", errContainerNotFound, name), + ) + } +} + +// ExecInDeployment runs the given command in a container belonging to the deployment with the given name. +// By default, it selects the first container of the first pod. +// +// Pod and container selection can be customized using [WithDeploymentPod], [WithDeploymentContainer], +// or related functions. +func (r *Resources) ExecInDeployment(ctx context.Context, namespaceName, deploymentName string, command []string, stdout, stderr *bytes.Buffer, opts ...DeploymentOption) error { + options := deploymentOptions{} + defaultOpts := []DeploymentOption{WithDeploymentPodIndex(0), WithDeploymentContainerIndex(0)} + for _, fn := range append(defaultOpts, opts...) { + fn(&options) + } + + var deployment appsv1.Deployment + if err := r.Get(ctx, deploymentName, namespaceName, &deployment); err != nil { + return err + } + + sel, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector) + if err != nil { + return err + } + + var pods v1.PodList + if err := r.client.List(ctx, &pods, cr.InNamespace(namespaceName), cr.MatchingLabelsSelector{Selector: sel}); err != nil { + return err + } + + pod, err := options.podMatcher(pods.Items) + if err != nil { + return err + } + + container, err := options.containerMatcher(pod.Spec.Containers) + if err != nil { + return err + } + + return r.ExecInPod(ctx, namespaceName, pod.Name, container.Name, command, stdout, stderr) +} + func init() { log.SetLogger(klog.NewKlogr()) } diff --git a/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go b/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go index 0154ada13..e627bb9d7 100644 --- a/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go +++ b/vendor/sigs.k8s.io/e2e-framework/klient/wait/conditions/conditions.go @@ -304,14 +304,15 @@ func (c *Condition) DeploymentAvailable(name, namespace string) apimachinerywait ) } -// DaemonSetReady is a helper function used to check if a daemonset's pods are scheduled and ready +// DaemonSetReady is a helper function used to check if a daemonset's pods are scheduled, ready and rolled out completely func (c *Condition) DaemonSetReady(daemonset k8s.Object) apimachinerywait.ConditionWithContextFunc { return func(ctx context.Context) (done bool, err error) { if err := c.resources.Get(ctx, daemonset.GetName(), daemonset.GetNamespace(), daemonset); err != nil { return false, err } - status := daemonset.(*appsv1.DaemonSet).Status // nolint: errcheck - if status.NumberReady == status.DesiredNumberScheduled && status.NumberUnavailable == 0 { + ds := daemonset.(*appsv1.DaemonSet) // nolint: errcheck + if ds.Status.NumberReady == ds.Status.DesiredNumberScheduled && + ds.Status.UpdatedNumberScheduled == ds.Status.DesiredNumberScheduled { done = true } return diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go b/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go index 50250eed7..95f46c343 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/env/env.go @@ -224,18 +224,22 @@ func (e *testEnv) processTestActions(ctx context.Context, t *testing.T, actions // AfterEachFeature. func (e *testEnv) processTestFeature(ctx context.Context, t *testing.T, featureName string, feature types.Feature) context.Context { t.Helper() - skipped, message := e.requireFeatureProcessing(feature) - if skipped { - t.Skip(message) - } - // execute beforeEachFeature actions - ctx = e.processFeatureActions(ctx, t, feature, e.getBeforeFeatureActions()) + t.Run(featureName, func(newT *testing.T) { + newT.Helper() + skipped, message := e.requireFeatureProcessing(feature) + if skipped { + newT.Skip(message) + } + // execute beforeEachFeature actions + ctx = e.processFeatureActions(ctx, t, feature, e.getBeforeFeatureActions()) - // execute feature test - ctx = e.execFeature(ctx, t, featureName, feature) + // execute feature test + ctx = e.execFeature(ctx, newT, feature) - // execute afterEachFeature actions - return e.processFeatureActions(ctx, t, feature, e.getAfterFeatureActions()) + // execute afterEachFeature actions + ctx = e.processFeatureActions(ctx, t, feature, e.getAfterFeatureActions()) + }) + return ctx } // processFeatureActions is used to run a series of feature action that were configured as @@ -477,72 +481,70 @@ func (e *testEnv) executeSteps(ctx context.Context, t *testing.T, steps []types. return ctx } -func (e *testEnv) execFeature(ctx context.Context, t *testing.T, featName string, f types.Feature) context.Context { +func (e *testEnv) execFeature(ctx context.Context, t *testing.T, f types.Feature) context.Context { t.Helper() - // feature-level subtest - t.Run(featName, func(newT *testing.T) { - newT.Helper() - if fDescription, ok := f.(types.DescribableFeature); ok && fDescription.Description() != "" { - t.Logf("Processing Feature: %s", fDescription.Description()) - } + if fDescription, ok := f.(types.DescribableFeature); ok && fDescription.Description() != "" { + t.Logf("Processing Feature: %s", fDescription.Description()) + } - // setups run at feature-level - setups := features.GetStepsByLevel(f.Steps(), types.LevelSetup) - ctx = e.executeSteps(ctx, newT, setups) + // setups run at feature-level + setups := features.GetStepsByLevel(f.Steps(), types.LevelSetup) + ctx = e.executeSteps(ctx, t, setups) - // assessments run as feature/assessment sub level - assessments := features.GetStepsByLevel(f.Steps(), types.LevelAssess) + // assessments run as feature/assessment sub level + assessments := features.GetStepsByLevel(f.Steps(), types.LevelAssess) - failed := false - for i, assess := range assessments { - assessName := assess.Name() - if dAssess, ok := assess.(types.DescribableStep); ok && dAssess.Description() != "" { - t.Logf("Processing Assessment: %s", dAssess.Description()) - } - if assessName == "" { - assessName = fmt.Sprintf("Assessment-%d", i+1) - } - // shouldFailNow catches whether t.FailNow() is called in the assessment. - // If it is, we won't proceed with the next assessment. - var shouldFailNow bool - newT.Run(assessName, func(internalT *testing.T) { - internalT.Helper() - skipped, message := e.requireAssessmentProcessing(assess, i+1) - if skipped { - internalT.Skip(message) - } - // Set shouldFailNow to true before actually running the assessment, because if the assessment - // calls t.FailNow(), the function will be abruptly stopped in the middle of `e.executeSteps()`. - shouldFailNow = true - ctx = e.executeSteps(ctx, internalT, []types.Step{assess}) - // If we reach this point, it means the assessment did not call t.FailNow(). - shouldFailNow = false - }) - // Check if the Test assessment under question performed either 2 things: - // - a t.FailNow() invocation - // - a `t.Fail()` or `t.Failed()` invocation - // In one of those cases, we need to track that and stop the next set of assessment in the feature - // under test from getting executed. - if shouldFailNow || (e.cfg.FailFast() && newT.Failed()) { - failed = true - break - } + failed := false + for i, assess := range assessments { + assessName := assess.Name() + if dAssess, ok := assess.(types.DescribableStep); ok && dAssess.Description() != "" { + t.Logf("Processing Assessment: %s", dAssess.Description()) } - - // Let us fail the test fast and not run the teardown in case if the framework specific fail-fast mode is - // invoked to make sure we leave the traces of the failed test behind to enable better debugging for the - // test developers - if e.cfg.FailFast() && failed { - newT.FailNow() + if assessName == "" { + assessName = fmt.Sprintf("Assessment-%d", i+1) + } + // shouldFailNow catches whether t.FailNow() is called in the assessment. + // If it is, we won't proceed with the next assessment. + var shouldFailNow bool + var wasSkipped bool + t.Run(assessName, func(internalT *testing.T) { + internalT.Helper() + defer func() { + wasSkipped = internalT.Skipped() + }() + skipped, message := e.requireAssessmentProcessing(assess, i+1) + if skipped { + internalT.Skip(message) + } + // Set shouldFailNow to true before actually running the assessment, because if the assessment + // calls t.FailNow(), the function will be abruptly stopped in the middle of `e.executeSteps()`. + shouldFailNow = true + ctx = e.executeSteps(ctx, internalT, []types.Step{assess}) + // If we reach this point, it means the assessment did not call t.FailNow(). + shouldFailNow = false + }) + // Check if the Test assessment under question performed either 2 things: + // - a t.FailNow() invocation + // - a `t.Fail()` or `t.Failed()` invocation + // In one of those cases, we need to track that and stop the next set of assessment in the feature + // under test from getting executed. Skipped tests should not trigger this. + if !wasSkipped && (shouldFailNow || (e.cfg.FailFast() && t.Failed())) { + failed = true + break } + } - // teardowns run at feature-level - teardowns := features.GetStepsByLevel(f.Steps(), types.LevelTeardown) - ctx = e.executeSteps(ctx, newT, teardowns) - }) + // Let us fail the test fast and not run the teardown in case if the framework specific fail-fast mode is + // invoked to make sure we leave the traces of the failed test behind to enable better debugging for the + // test developers + if e.cfg.FailFast() && failed { + t.FailNow() + } - return ctx + // teardowns run at feature-level + teardowns := features.GetStepsByLevel(f.Steps(), types.LevelTeardown) + return e.executeSteps(ctx, t, teardowns) } // requireFeatureProcessing is a wrapper around the requireProcessing function to process the feature level validation @@ -649,7 +651,7 @@ func (e *testEnv) deepCopyConfig() *envconf.Config { labels := make(map[string][]string, len(e.cfg.Labels())) for k, vals := range e.cfg.Labels() { - copyVals := make([]string, len(vals)) + copyVals := make([]string, 0, len(vals)) copyVals = append(copyVals, vals...) labels[k] = copyVals } @@ -657,7 +659,7 @@ func (e *testEnv) deepCopyConfig() *envconf.Config { skipLabels := make(map[string][]string, len(e.cfg.SkipLabels())) for k, vals := range e.cfg.SkipLabels() { - copyVals := make([]string, len(vals)) + copyVals := make([]string, 0, len(vals)) copyVals = append(copyVals, vals...) skipLabels[k] = copyVals } diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go b/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go index ec7283342..0fa992b19 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/envconf/config.go @@ -28,7 +28,7 @@ import ( "sigs.k8s.io/e2e-framework/pkg/flags" ) -// Config represents and environment configuration +// Config represents the environment configuration type Config struct { client klient.Client kubeconfig string @@ -100,7 +100,7 @@ func (c *Config) KubeconfigFile() string { return c.kubeconfig } -// WithClient used to update the environment klient.Client +// WithClient updates the environment klient.Client func (c *Config) WithClient(client klient.Client) *Config { c.client = client return c @@ -112,7 +112,7 @@ func (c *Config) GetClient() klient.Client { } // NewClient is a constructor function that returns a previously -// created klient.Client or create a new one based on configuration +// created klient.Client or creates a new one based on configuration // previously set. Will return an error if unable to do so. func (c *Config) NewClient() (klient.Client, error) { if c.client != nil { @@ -258,7 +258,7 @@ func (c *Config) WithFailFast() *Config { return c } -// FailFast indicate if the framework is running in fail fast mode. This +// FailFast indicates if the framework is running in fail fast mode. This // controls the behavior of how the assessments and features are handled // if a test encounters a failure result func (c *Config) FailFast() bool { diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/types/types.go b/vendor/sigs.k8s.io/e2e-framework/pkg/types/types.go index ef640e9c5..1a5788e46 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/types/types.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/types/types.go @@ -196,6 +196,11 @@ type E2EClusterProvider interface { // using the cluster provider native way GetKubeconfig() string + // GenerateKubeconfig provides a way to extract the kubeconfig file associated with the cluster in question. + // Different from GetKubeconfig, this method is used to extract the kubeconfig using provider native way, where the GetKubeconfig is cached. + // It is useful in cases where the kubeconfig file is required to be extracted multiple times during the test run with different arguments. + GenerateKubeconfig(args ...string) (string, error) + // GetKubectlContext is used to extract the kubectl context to be used while performing the operation GetKubectlContext() string diff --git a/vendor/sigs.k8s.io/e2e-framework/pkg/utils/command.go b/vendor/sigs.k8s.io/e2e-framework/pkg/utils/command.go index b2a375513..de95e354c 100644 --- a/vendor/sigs.k8s.io/e2e-framework/pkg/utils/command.go +++ b/vendor/sigs.k8s.io/e2e-framework/pkg/utils/command.go @@ -18,6 +18,7 @@ package utils import ( "bytes" + "context" "fmt" "io" @@ -93,15 +94,33 @@ func FindOrInstallGoBasedProvider(pPath, provider, module, version string) (stri return "", fmt.Errorf("%s not available even after installation", provider) } -// RunCommand run command and returns an *exec.Proc with information about the executed process. +// RunCommand runs the command and returns an *exec.Proc with information about the executed process. +// +// Deprecated: Please use RunCommandContext instead and provide a context to +// make sure the command execution can be cancelled if needed. func RunCommand(command string) *exec.Proc { - return commandRunner.RunProc(command) + ctx := context.Background() + return RunCommandContext(ctx, command) +} + +// RunCommand runs the command with the provided context and returns an +// *exec.Proc with information about the executed process. +func RunCommandContext(ctx context.Context, command string) *exec.Proc { + return commandRunner.RunProcWithContext(ctx, command) } // RunCommandWithSeperatedOutput run command and returns the results to the provided // stdout and stderr io.Writer. +// +// Deprecated: Use RunCommandWithSeperatedOutputContext instead and provide a context to. func RunCommandWithSeperatedOutput(command string, stdout, stderr io.Writer) error { - p := commandRunner.NewProc(command) + return RunCommandWithSeperatedOutputContext(context.Background(), command, stdout, stderr) +} + +// RunCommandWithSeperatedOutputContext run command with the provided context +// and returns the results to the provided stdout and stderr io.Writer. +func RunCommandWithSeperatedOutputContext(ctx context.Context, command string, stdout, stderr io.Writer) error { + p := commandRunner.NewProcWithContext(ctx, command) p.SetStdout(stdout) p.SetStderr(stderr) result := p.Run() @@ -109,10 +128,19 @@ func RunCommandWithSeperatedOutput(command string, stdout, stderr io.Writer) err return result.Err() } -// RunCommandWithCustomWriter run command and returns an *exec.Proc with information about the executed process. +// RunCommandWithCustomWriterContext run command and returns an *exec.Proc with information about the executed process. // This helps map the STDOUT/STDERR to custom writer to extract data from the output. +// +// Deprecated: Use RunCommandWithCustomWriterContext instead and provide a +// context to make sure the command execution can be cancelled if needed. func RunCommandWithCustomWriter(command string, stdout, stderr io.Writer) *exec.Proc { - p := commandRunner.NewProc(command) + return RunCommandWithCustomWriterContext(context.Background(), command, stdout, stderr) +} + +// RunCommandWithCustomWriterContext run command and returns an *exec.Proc with information about the executed process. +// This helps map the STDOUT/STDERR to custom writer to extract data from the output. +func RunCommandWithCustomWriterContext(ctx context.Context, command string, stdout, stderr io.Writer) *exec.Proc { + p := commandRunner.NewProcWithContext(ctx, command) p.SetStdout(stdout) p.SetStderr(stderr) return p.Run() diff --git a/vendor/sigs.k8s.io/e2e-framework/third_party/kind/kind.go b/vendor/sigs.k8s.io/e2e-framework/third_party/kind/kind.go index f79c4299f..31873f0a7 100644 --- a/vendor/sigs.k8s.io/e2e-framework/third_party/kind/kind.go +++ b/vendor/sigs.k8s.io/e2e-framework/third_party/kind/kind.go @@ -107,11 +107,11 @@ func (k *Cluster) WithOpts(opts ...support.ClusterOpts) support.E2EClusterProvid return k } -func (k *Cluster) getKubeconfig() (string, error) { +func (k *Cluster) getKubeconfig(ctx context.Context, args ...string) (string, error) { kubecfg := fmt.Sprintf("%s-kubecfg", k.name) var stdout, stderr bytes.Buffer - err := utils.RunCommandWithSeperatedOutput(fmt.Sprintf(`%s get kubeconfig --name %s`, k.path, k.name), &stdout, &stderr) + err := utils.RunCommandWithSeperatedOutputContext(ctx, fmt.Sprintf(`%s get kubeconfig %s --name %s`, k.path, strings.Join(args, " "), k.name), &stdout, &stderr) if err != nil { return "", fmt.Errorf("kind get kubeconfig: stderr: %s: %w", stderr.String(), err) } @@ -158,7 +158,7 @@ func (k *Cluster) Create(ctx context.Context, args ...string) (string, error) { if _, ok := k.clusterExists(k.name); ok { log.V(4).Info("Skipping Kind Cluster.Create: cluster already created: ", k.name) - kConfig, err := k.getKubeconfig() + kConfig, err := k.getKubeconfig(ctx) if err != nil { return "", err } @@ -174,7 +174,7 @@ func (k *Cluster) Create(ctx context.Context, args ...string) (string, error) { command = fmt.Sprintf("%s %s", command, strings.Join(args, " ")) } log.V(4).Info("Launching:", command) - p := utils.RunCommand(command) + p := utils.RunCommandContext(ctx, command) if p.Err() != nil { outBytes, err := io.ReadAll(p.Out()) if err != nil { @@ -188,7 +188,7 @@ func (k *Cluster) Create(ctx context.Context, args ...string) (string, error) { } log.V(4).Info("kind clusters available: ", clusters) - kConfig, err := k.getKubeconfig() + kConfig, err := k.getKubeconfig(ctx) if err != nil { return "", err } @@ -219,7 +219,7 @@ func (k *Cluster) ExportLogs(ctx context.Context, dest string) error { return err } - p := utils.RunCommand(fmt.Sprintf(`%s export logs %s --name %s`, k.path, dest, k.name)) + p := utils.RunCommandContext(ctx, fmt.Sprintf(`%s export logs %s --name %s`, k.path, dest, k.name)) if p.Err() != nil { return fmt.Errorf("kind: export cluster %v logs failed: %s: %s", k.name, p.Err(), p.Result()) } @@ -233,7 +233,7 @@ func (k *Cluster) Destroy(ctx context.Context) error { return err } - p := utils.RunCommand(fmt.Sprintf(`%s delete cluster --name %s`, k.path, k.name)) + p := utils.RunCommandContext(ctx, fmt.Sprintf(`%s delete cluster --name %s`, k.path, k.name)) if p.Err() != nil { outBytes, err := io.ReadAll(p.Out()) if err != nil { @@ -262,7 +262,7 @@ func (k *Cluster) findOrInstallKind() error { } func (k *Cluster) LoadImage(ctx context.Context, image string, args ...string) error { - p := utils.RunCommand(fmt.Sprintf(`%s load docker-image --name %s %s`, k.path, k.name, image)) + p := utils.RunCommandContext(ctx, fmt.Sprintf(`%s load docker-image --name %s %s`, k.path, k.name, image)) if p.Err() != nil { return fmt.Errorf("kind: load docker-image %v failed: %s: %s", image, p.Err(), p.Result()) } @@ -270,7 +270,7 @@ func (k *Cluster) LoadImage(ctx context.Context, image string, args ...string) e } func (k *Cluster) LoadImageArchive(ctx context.Context, imageArchive string, args ...string) error { - p := utils.RunCommand(fmt.Sprintf(`%s load image-archive --name %s %s`, k.path, k.name, imageArchive)) + p := utils.RunCommandContext(ctx, fmt.Sprintf(`%s load image-archive --name %s %s`, k.path, k.name, imageArchive)) if p.Err() != nil { return fmt.Errorf("kind: load image-archive %v failed: %s: %s", imageArchive, p.Err(), p.Result()) } @@ -307,3 +307,7 @@ func (k *Cluster) WaitForControlPlane(ctx context.Context, client klient.Client) func (k *Cluster) KubernetesRestConfig() *rest.Config { return k.rc } + +func (k *Cluster) GenerateKubeconfig(args ...string) (string, error) { + return k.getKubeconfig(context.Background(), args...) +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v6/schema/elements.go b/vendor/sigs.k8s.io/structured-merge-diff/v6/schema/elements.go index 5d3707a5b..c8138a654 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/v6/schema/elements.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/v6/schema/elements.go @@ -18,6 +18,7 @@ package schema import ( "sync" + "sync/atomic" ) // Schema is a list of named types. @@ -28,7 +29,7 @@ type Schema struct { Types []TypeDef `yaml:"types,omitempty"` once sync.Once - m map[string]TypeDef + m atomic.Pointer[map[string]TypeDef] lock sync.Mutex // Cached results of resolving type references to atoms. Only stores @@ -144,26 +145,28 @@ type Map struct { ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"` once sync.Once - m map[string]StructField + m atomic.Pointer[map[string]StructField] } // FindField is a convenience function that returns the referenced StructField, // if it exists, or (nil, false) if it doesn't. func (m *Map) FindField(name string) (StructField, bool) { m.once.Do(func() { - m.m = make(map[string]StructField, len(m.Fields)) + mm := make(map[string]StructField, len(m.Fields)) for _, field := range m.Fields { - m.m[field.Name] = field + mm[field.Name] = field } + m.m.Store(&mm) }) - sf, ok := m.m[name] + sf, ok := (*m.m.Load())[name] return sf, ok } -// CopyInto this instance of Map into the other -// If other is nil this method does nothing. -// If other is already initialized, overwrites it with this instance -// Warning: Not thread safe +// CopyInto clones this instance of Map into dst +// +// If dst is nil this method does nothing. +// If dst is already initialized, overwrites it with this instance. +// Warning: Not thread safe. Only use dst after this function returns. func (m *Map) CopyInto(dst *Map) { if dst == nil { return @@ -175,12 +178,13 @@ func (m *Map) CopyInto(dst *Map) { dst.Unions = m.Unions dst.ElementRelationship = m.ElementRelationship - if m.m != nil { + mm := m.m.Load() + if mm != nil { // If cache is non-nil then the once token had been consumed. // Must reset token and use it again to ensure same semantics. dst.once = sync.Once{} dst.once.Do(func() { - dst.m = m.m + dst.m.Store(mm) }) } } @@ -274,12 +278,13 @@ type List struct { // if it exists, or (nil, false) if it doesn't. func (s *Schema) FindNamedType(name string) (TypeDef, bool) { s.once.Do(func() { - s.m = make(map[string]TypeDef, len(s.Types)) + sm := make(map[string]TypeDef, len(s.Types)) for _, t := range s.Types { - s.m[t.Name] = t + sm[t.Name] = t } + s.m.Store(&sm) }) - t, ok := s.m[name] + t, ok := (*s.m.Load())[name] return t, ok } @@ -352,10 +357,11 @@ func (s *Schema) Resolve(tr TypeRef) (Atom, bool) { return result, true } -// Clones this instance of Schema into the other -// If other is nil this method does nothing. -// If other is already initialized, overwrites it with this instance -// Warning: Not thread safe +// CopyInto clones this instance of Schema into dst +// +// If dst is nil this method does nothing. +// If dst is already initialized, overwrites it with this instance. +// Warning: Not thread safe. Only use dst after this function returns. func (s *Schema) CopyInto(dst *Schema) { if dst == nil { return @@ -364,12 +370,13 @@ func (s *Schema) CopyInto(dst *Schema) { // Schema type is considered immutable so sharing references dst.Types = s.Types - if s.m != nil { + sm := s.m.Load() + if sm != nil { // If cache is non-nil then the once token had been consumed. // Must reset token and use it again to ensure same semantics. dst.once = sync.Once{} dst.once.Do(func() { - dst.m = s.m + dst.m.Store(sm) }) } } diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v6/typed/remove.go b/vendor/sigs.k8s.io/structured-merge-diff/v6/typed/remove.go index 86de5105d..0db1734f9 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/v6/typed/remove.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/v6/typed/remove.go @@ -58,6 +58,10 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) { defer w.allocator.Free(l) // If list is null or empty just return if l == nil || l.Length() == 0 { + // For extraction, we just return the value as is (which is nil or empty). For extraction the difference matters. + if w.shouldExtract { + w.out = w.value.Unstructured() + } return nil } @@ -71,6 +75,7 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) { } var newItems []interface{} + hadMatches := false iter := l.RangeUsing(w.allocator) defer w.allocator.Free(iter) for iter.Next() { @@ -80,24 +85,40 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) { path, _ := fieldpath.MakePath(pe) // save items on the path when we shouldExtract // but ignore them when we are removing (i.e. !w.shouldExtract) - if w.toRemove.Has(path) { - if w.shouldExtract { - newItems = append(newItems, removeItemsWithSchema(item, w.toRemove, w.schema, t.ElementType, w.shouldExtract).Unstructured()) - } else { - continue + isExactPathMatch := w.toRemove.Has(path) + isPrefixMatch := !w.toRemove.WithPrefix(pe).Empty() + if w.shouldExtract { + if isPrefixMatch { + item = removeItemsWithSchema(item, w.toRemove.WithPrefix(pe), w.schema, t.ElementType, w.shouldExtract) + } + if isExactPathMatch || isPrefixMatch { + newItems = append(newItems, item.Unstructured()) } - } - if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { - item = removeItemsWithSchema(item, subset, w.schema, t.ElementType, w.shouldExtract) } else { - // don't save items not on the path when we shouldExtract. - if w.shouldExtract { + if isExactPathMatch { continue } + if isPrefixMatch { + // Removing nested items within this list item and preserve if it becomes empty + hadMatches = true + wasMap := item.IsMap() + wasList := item.IsList() + item = removeItemsWithSchema(item, w.toRemove.WithPrefix(pe), w.schema, t.ElementType, w.shouldExtract) + // If item returned null but we're removing items within the structure(not the item itself), + // preserve the empty container structure + if item.IsNull() && !w.shouldExtract { + if wasMap { + item = value.NewValueInterface(map[string]interface{}{}) + } else if wasList { + item = value.NewValueInterface([]interface{}{}) + } + } + } + newItems = append(newItems, item.Unstructured()) } - newItems = append(newItems, item.Unstructured()) } - if len(newItems) > 0 { + // Preserve empty lists (non-nil) instead of converting to null when items were matched and removed + if len(newItems) > 0 || (hadMatches && !w.shouldExtract) { w.out = newItems } return nil @@ -113,6 +134,10 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors { } // If map is null or empty just return if m == nil || m.Empty() { + // For extraction, we just return the value as is (which is nil or empty). For extraction the difference matters. + if w.shouldExtract { + w.out = w.value.Unstructured() + } return nil } @@ -131,6 +156,7 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors { } newMap := map[string]interface{}{} + hadMatches := false m.Iterate(func(k string, val value.Value) bool { pe := fieldpath.PathElement{FieldName: &k} path, _ := fieldpath.MakePath(pe) @@ -148,7 +174,19 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors { return true } if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { + hadMatches = true + wasMap := val.IsMap() + wasList := val.IsList() val = removeItemsWithSchema(val, subset, w.schema, fieldType, w.shouldExtract) + // If val returned null but we're removing items within the structure (not the field itself), + // preserve the empty container structure + if val.IsNull() && !w.shouldExtract { + if wasMap { + val = value.NewValueInterface(map[string]interface{}{}) + } else if wasList { + val = value.NewValueInterface([]interface{}{}) + } + } } else { // don't save values not on the path when we shouldExtract. if w.shouldExtract { @@ -158,7 +196,8 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors { newMap[k] = val.Unstructured() return true }) - if len(newMap) > 0 { + // Preserve empty maps (non-nil) instead of converting to null when items were matched and removed + if len(newMap) > 0 || (hadMatches && !w.shouldExtract) { w.out = newMap } return nil diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v6/value/reflectcache.go b/vendor/sigs.k8s.io/structured-merge-diff/v6/value/reflectcache.go index 3b4a402ee..75b7085c3 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/v6/value/reflectcache.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/v6/value/reflectcache.go @@ -84,6 +84,10 @@ func (f *FieldCacheEntry) CanOmit(fieldVal reflect.Value) bool { func (f *FieldCacheEntry) GetFrom(structVal reflect.Value) reflect.Value { // field might be nested within 'inline' structs for _, elem := range f.fieldPath { + if safeIsNil(structVal) { + // if any part of the path is nil, return the zero value for the field type + return reflect.Zero(f.fieldType) + } structVal = dereference(structVal).FieldByIndex(elem) } return structVal diff --git a/vendor/sigs.k8s.io/yaml/goyaml.v2/README.md b/vendor/sigs.k8s.io/yaml/goyaml.v2/README.md deleted file mode 100644 index 9a8f1e678..000000000 --- a/vendor/sigs.k8s.io/yaml/goyaml.v2/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# goyaml.v2 - -This package provides type and function aliases for the `go.yaml.in/yaml/v2` package (which is compatible with `gopkg.in/yaml.v2`). - -## Purpose - -The purpose of this package is to: - -1. Provide a transition path for users migrating from the sigs.k8s.io/yaml package to direct usage of go.yaml.in/yaml/v2 -2. Maintain compatibility with existing code while encouraging migration to the upstream package -3. Reduce maintenance overhead by delegating to the upstream implementation - -## Usage - -Instead of importing this package directly, you should migrate to using `go.yaml.in/yaml/v2` directly: - -```go -// Old way -import "sigs.k8s.io/yaml/goyaml.v2" - -// Recommended way -import "go.yaml.in/yaml/v2" -``` - -## Available Types and Functions - -All public types and functions from `go.yaml.in/yaml/v2` are available through this package: - -### Types - -- `MapSlice` - Encodes and decodes as a YAML map with preserved key order -- `MapItem` - An item in a MapSlice -- `Unmarshaler` - Interface for custom unmarshaling behavior -- `Marshaler` - Interface for custom marshaling behavior -- `IsZeroer` - Interface to check if an object is zero -- `Decoder` - Reads and decodes YAML values from an input stream -- `Encoder` - Writes YAML values to an output stream -- `TypeError` - Error returned by Unmarshal for decoding issues - -### Functions - -- `Unmarshal` - Decodes YAML data into a Go value -- `UnmarshalStrict` - Like Unmarshal but errors on unknown fields -- `Marshal` - Serializes a Go value into YAML -- `NewDecoder` - Creates a new Decoder -- `NewEncoder` - Creates a new Encoder -- `FutureLineWrap` - Controls line wrapping behavior - -## Migration Guide - -To migrate from this package to `go.yaml.in/yaml/v2`: - -1. Update your import statements: - ```go - // From - import "sigs.k8s.io/yaml/goyaml.v2" - - // To - import "go.yaml.in/yaml/v2" - ``` - -2. No code changes should be necessary as the API is identical - -3. Update your go.mod file to include the dependency: - ``` - require go.yaml.in/yaml/v2 v2.4.2 - ``` - -## Deprecation Notice - -All types and functions in this package are marked as deprecated. You should migrate to using `go.yaml.in/yaml/v2` directly. diff --git a/vendor/sigs.k8s.io/yaml/goyaml.v2/yaml_aliases.go b/vendor/sigs.k8s.io/yaml/goyaml.v2/yaml_aliases.go deleted file mode 100644 index 8c82bc2cb..000000000 --- a/vendor/sigs.k8s.io/yaml/goyaml.v2/yaml_aliases.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2025 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 yaml - -import ( - gopkg_yaml "go.yaml.in/yaml/v2" -) - -// Type aliases for public types from go.yaml.in/yaml/v2 -type ( - // MapSlice encodes and decodes as a YAML map. - // The order of keys is preserved when encoding and decoding. - // Deprecated: Use go.yaml.in/yaml/v2.MapSlice directly. - MapSlice = gopkg_yaml.MapSlice - - // MapItem is an item in a MapSlice. - // Deprecated: Use go.yaml.in/yaml/v2.MapItem directly. - MapItem = gopkg_yaml.MapItem - - // Unmarshaler is implemented by types to customize their behavior when being unmarshaled from a YAML document. - // Deprecated: Use go.yaml.in/yaml/v2.Unmarshaler directly. - Unmarshaler = gopkg_yaml.Unmarshaler - - // Marshaler is implemented by types to customize their behavior when being marshaled into a YAML document. - // Deprecated: Use go.yaml.in/yaml/v2.Marshaler directly. - Marshaler = gopkg_yaml.Marshaler - - // IsZeroer is used to check whether an object is zero to determine whether it should be omitted when - // marshaling with the omitempty flag. One notable implementation is time.Time. - // Deprecated: Use go.yaml.in/yaml/v2.IsZeroer directly. - IsZeroer = gopkg_yaml.IsZeroer - - // Decoder reads and decodes YAML values from an input stream. - // Deprecated: Use go.yaml.in/yaml/v2.Decoder directly. - Decoder = gopkg_yaml.Decoder - - // Encoder writes YAML values to an output stream. - // Deprecated: Use go.yaml.in/yaml/v2.Encoder directly. - Encoder = gopkg_yaml.Encoder - - // TypeError is returned by Unmarshal when one or more fields in the YAML document cannot be properly decoded. - // Deprecated: Use go.yaml.in/yaml/v2.TypeError directly. - TypeError = gopkg_yaml.TypeError -) - -// Function aliases for public functions from go.yaml.in/yaml/v2 -var ( - // Unmarshal decodes the first document found within the in byte slice and assigns decoded values into the out value. - // Deprecated: Use go.yaml.in/yaml/v2.Unmarshal directly. - Unmarshal = gopkg_yaml.Unmarshal - - // UnmarshalStrict is like Unmarshal except that any fields that are found in the data that do not have corresponding struct members will result in an error. - // Deprecated: Use go.yaml.in/yaml/v2.UnmarshalStrict directly. - UnmarshalStrict = gopkg_yaml.UnmarshalStrict - - // Marshal serializes the value provided into a YAML document. - // Deprecated: Use go.yaml.in/yaml/v2.Marshal directly. - Marshal = gopkg_yaml.Marshal - - // NewDecoder returns a new decoder that reads from r. - // Deprecated: Use go.yaml.in/yaml/v2.NewDecoder directly. - NewDecoder = gopkg_yaml.NewDecoder - - // NewEncoder returns a new encoder that writes to w. - // Deprecated: Use go.yaml.in/yaml/v2.NewEncoder directly. - NewEncoder = gopkg_yaml.NewEncoder - - // FutureLineWrap globally disables line wrapping when encoding long strings. - // Deprecated: Use go.yaml.in/yaml/v2.FutureLineWrap directly. - FutureLineWrap = gopkg_yaml.FutureLineWrap -)