├── pkg ├── features │ ├── OWNERS │ ├── register │ │ └── register.go │ └── kube_features.go ├── leadermigration │ ├── util.go │ ├── config │ │ ├── default.go │ │ ├── config.go │ │ └── config_test.go │ ├── filter.go │ ├── migrator.go │ ├── options │ │ ├── options.go │ │ └── options_test.go │ └── migrator_test.go ├── healthz │ ├── healthz_test.go │ ├── healthz.go │ ├── handler.go │ └── handler_test.go ├── informerfactory │ └── informer_factory.go └── clientbuilder │ ├── client_builder.go │ └── client_builder_dynamic.go ├── code-of-conduct.md ├── .github └── PULL_REQUEST_TEMPLATE.md ├── config ├── OWNERS ├── doc.go ├── v1 │ ├── doc.go │ ├── register.go │ ├── types.go │ ├── zz_generated.deepcopy.go │ └── conversion.go ├── v1beta1 │ ├── doc.go │ ├── zz_generated.model_name.go │ ├── register.go │ ├── types.go │ ├── zz_generated.deepcopy.go │ └── zz_generated.conversion.go ├── v1alpha1 │ ├── doc.go │ ├── zz_generated.model_name.go │ ├── register.go │ ├── defaults.go │ ├── conversion.go │ ├── zz_generated.deepcopy.go │ ├── types.go │ └── zz_generated.conversion.go ├── register.go ├── zz_generated.deepcopy.go └── types.go ├── OWNERS ├── SECURITY_CONTACTS ├── doc.go ├── CONTRIBUTING.md ├── README.md ├── controller └── interfaces.go ├── options ├── debugging.go ├── generic.go └── generic_test.go ├── app ├── helper.go ├── controllercontext.go ├── serve.go └── helper_test.go ├── go.mod ├── LICENSE └── go.sum /pkg/features/OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - feature-approvers 5 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Sorry, we do not accept changes directly against this repository. Please see 2 | CONTRIBUTING.md for information on where and how to contribute instead. 3 | -------------------------------------------------------------------------------- /config/OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - api-approvers 3 | - deads2k 4 | - luxas 5 | - mtaufen 6 | - sttts 7 | reviewers: 8 | - api-reviewers 9 | - deads2k 10 | - luxas 11 | - mtaufen 12 | - sttts 13 | emeritus_approvers: 14 | - stewart-yu 15 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - andrewsykim 5 | - cheftako 6 | - deads2k 7 | - liggitt 8 | - mtaufen 9 | - sttts 10 | - jpbetz 11 | reviewers: 12 | - andrewsykim 13 | - cheftako 14 | - cici37 15 | - deads2k 16 | - jiahuif 17 | - jpbetz 18 | - liggitt 19 | - luxas 20 | - mtaufen 21 | - sttts 22 | - jpbetz 23 | labels: 24 | - sig/api-machinery 25 | - sig/cloud-provider 26 | emeritus_approvers: 27 | - lavalamp 28 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Committee to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | cjcullen 14 | joelsmith 15 | liggitt 16 | luxas 17 | sttts 18 | tallclair 19 | -------------------------------------------------------------------------------- /config/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | 19 | package config 20 | -------------------------------------------------------------------------------- /config/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +groupName=controllermanager.config.k8s.io 19 | 20 | package v1 21 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package controllermanager contains the common library code for 18 | // building a controller-manager. It was based on the common code 19 | // between the kube-controller-manager and the cloud-controller-manager. 20 | package controllermanager 21 | -------------------------------------------------------------------------------- /config/v1beta1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +k8s:conversion-gen=k8s.io/controller-manager/config 19 | // +k8s:openapi-gen=true 20 | // +k8s:openapi-model-package=io.k8s.controller-manager.config.v1beta1 21 | 22 | // +groupName=controllermanager.config.k8s.io 23 | 24 | package v1beta1 25 | -------------------------------------------------------------------------------- /pkg/features/register/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package register 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/util/runtime" 21 | utilfeature "k8s.io/apiserver/pkg/util/feature" 22 | "k8s.io/controller-manager/pkg/features" 23 | ) 24 | 25 | func init() { 26 | runtime.Must(features.SetupCurrentKubernetesSpecificFeatureGates(utilfeature.DefaultMutableFeatureGate)) 27 | } 28 | -------------------------------------------------------------------------------- /config/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package 18 | // +k8s:conversion-gen=k8s.io/controller-manager/config 19 | // +k8s:conversion-gen=k8s.io/controller-manager/config/v1alpha1 20 | // +k8s:openapi-gen=true 21 | // +k8s:openapi-model-package=io.k8s.controller-manager.config.v1alpha1 22 | 23 | // +groupName=controllermanager.config.k8s.io 24 | 25 | package v1alpha1 26 | -------------------------------------------------------------------------------- /pkg/leadermigration/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package leadermigration 18 | 19 | import config "k8s.io/controller-manager/config" 20 | 21 | // Enabled checks whether Leader Migration should be enabled, given the GenericControllerManagerConfiguration. 22 | // It considers the feature gate first, and will always return false if the feature gate is not enabled. 23 | func Enabled(genericConfig *config.GenericControllerManagerConfiguration) bool { 24 | return genericConfig.LeaderElection.LeaderElect && genericConfig.LeaderMigrationEnabled 25 | } 26 | -------------------------------------------------------------------------------- /config/v1beta1/zz_generated.model_name.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by openapi-gen. DO NOT EDIT. 21 | 22 | package v1beta1 23 | 24 | // OpenAPIModelName returns the OpenAPI model name for this type. 25 | func (in ControllerLeaderConfiguration) OpenAPIModelName() string { 26 | return "io.k8s.controller-manager.config.v1beta1.ControllerLeaderConfiguration" 27 | } 28 | 29 | // OpenAPIModelName returns the OpenAPI model name for this type. 30 | func (in LeaderMigrationConfiguration) OpenAPIModelName() string { 31 | return "io.k8s.controller-manager.config.v1beta1.LeaderMigrationConfiguration" 32 | } 33 | -------------------------------------------------------------------------------- /pkg/healthz/healthz_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package healthz 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | "testing" 23 | ) 24 | 25 | type checkWithMessage struct { 26 | message string 27 | } 28 | 29 | func (c *checkWithMessage) Check(_ *http.Request) error { 30 | return fmt.Errorf("%s", c.message) 31 | } 32 | 33 | func TestNamedHealthChecker(t *testing.T) { 34 | named := NamedHealthChecker("foo", &checkWithMessage{message: "hello"}) 35 | if named.Name() != "foo" { 36 | t.Errorf("expected: %v, got: %v", "foo", named.Name()) 37 | } 38 | if err := named.Check(nil); err.Error() != "hello" { 39 | t.Errorf("expected: %v, got: %v", "hello", err.Error()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/leadermigration/config/default.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import internal "k8s.io/controller-manager/config" 20 | 21 | // DefaultLeaderMigrationConfiguration returns the default LeaderMigrationConfiguration 22 | // that is valid for this release of Kubernetes. 23 | func DefaultLeaderMigrationConfiguration() *internal.LeaderMigrationConfiguration { 24 | return &internal.LeaderMigrationConfiguration{ 25 | LeaderName: "cloud-provider-extraction-migration", 26 | ResourceLock: ResourceLockLeases, 27 | ControllerLeaders: []internal.ControllerLeaderConfiguration{ 28 | { 29 | Name: "route-controller", 30 | Component: "*", 31 | }, { 32 | Name: "service-controller", 33 | Component: "*", 34 | }, { 35 | Name: "cloud-node-lifecycle-controller", 36 | Component: "*", 37 | }, 38 | }, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/leadermigration/filter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package leadermigration 18 | 19 | // FilterResult indicates whether and how the controller manager should start the controller. 20 | type FilterResult int32 21 | 22 | const ( 23 | // ControllerUnowned indicates that the controller is owned by another controller manager 24 | // and thus should NOT be started by this controller manager. 25 | ControllerUnowned = iota 26 | // ControllerMigrated indicates that the controller manager should start this controller 27 | // with the migration lock. 28 | ControllerMigrated 29 | // ControllerNonMigrated indicates that the controller manager should start this controller 30 | // with the main lock. 31 | ControllerNonMigrated 32 | ) 33 | 34 | // FilterFunc takes a name of controller, returning a FilterResult indicating how to start controller. 35 | type FilterFunc func(controllerName string) FilterResult 36 | -------------------------------------------------------------------------------- /pkg/healthz/healthz.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package healthz 18 | 19 | import ( 20 | "net/http" 21 | 22 | "k8s.io/apiserver/pkg/server/healthz" 23 | ) 24 | 25 | // NamedPingChecker returns a health check with given name 26 | // that returns no error when checked. 27 | func NamedPingChecker(name string) healthz.HealthChecker { 28 | return NamedHealthChecker(name, healthz.PingHealthz) 29 | } 30 | 31 | // NamedHealthChecker creates a named health check from 32 | // an unnamed one. 33 | func NamedHealthChecker(name string, check UnnamedHealthChecker) healthz.HealthChecker { 34 | return healthz.NamedCheck(name, check.Check) 35 | } 36 | 37 | // UnnamedHealthChecker is an unnamed healthz checker. 38 | // The name of the check can be set by the controller manager. 39 | type UnnamedHealthChecker interface { 40 | Check(req *http.Request) error 41 | } 42 | 43 | var _ UnnamedHealthChecker = (healthz.HealthChecker)(nil) 44 | -------------------------------------------------------------------------------- /config/v1alpha1/zz_generated.model_name.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by openapi-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | // OpenAPIModelName returns the OpenAPI model name for this type. 25 | func (in ControllerLeaderConfiguration) OpenAPIModelName() string { 26 | return "io.k8s.controller-manager.config.v1alpha1.ControllerLeaderConfiguration" 27 | } 28 | 29 | // OpenAPIModelName returns the OpenAPI model name for this type. 30 | func (in GenericControllerManagerConfiguration) OpenAPIModelName() string { 31 | return "io.k8s.controller-manager.config.v1alpha1.GenericControllerManagerConfiguration" 32 | } 33 | 34 | // OpenAPIModelName returns the OpenAPI model name for this type. 35 | func (in LeaderMigrationConfiguration) OpenAPIModelName() string { 36 | return "io.k8s.controller-manager.config.v1alpha1.LeaderMigrationConfiguration" 37 | } 38 | -------------------------------------------------------------------------------- /config/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/runtime" 21 | "k8s.io/apimachinery/pkg/runtime/schema" 22 | ) 23 | 24 | // GroupName is the "group" that is needed to uniquely identify the API 25 | const GroupName = "controllermanager.config.k8s.io" 26 | 27 | // SchemeGroupVersion is group version used to register these objects 28 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} 29 | 30 | var ( 31 | // SchemeBuilder is the scheme builder with scheme init functions to run for this API package 32 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 33 | // localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package, 34 | // defaulting and conversion init funcs are registered as well. 35 | localSchemeBuilder = &SchemeBuilder 36 | // AddToScheme is a global function that registers this API group & version to a scheme 37 | AddToScheme = localSchemeBuilder.AddToScheme 38 | ) 39 | 40 | // Adds the list of known types to the given scheme. 41 | func addKnownTypes(scheme *runtime.Scheme) error { 42 | scheme.AddKnownTypes(SchemeGroupVersion, 43 | &LeaderMigrationConfiguration{}, 44 | ) 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /config/v1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | ) 24 | 25 | // GroupName is the "group" that is needed to uniquely identify the API 26 | const GroupName = "controllermanager.config.k8s.io" 27 | 28 | // SchemeGroupVersion is group version used to register these objects 29 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} 30 | 31 | var ( 32 | // SchemeBuilder is the scheme builder with scheme init functions to run for this API package 33 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 34 | // localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package, 35 | // defaulting and conversion init funcs are registered as well. 36 | localSchemeBuilder = &SchemeBuilder 37 | // AddToScheme is a global function that registers this API group & version to a scheme 38 | AddToScheme = localSchemeBuilder.AddToScheme 39 | ) 40 | 41 | // Adds the list of known types to the given scheme. 42 | func addKnownTypes(scheme *runtime.Scheme) error { 43 | scheme.AddKnownTypes(SchemeGroupVersion, 44 | &LeaderMigrationConfiguration{}, 45 | ) 46 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /config/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | ) 24 | 25 | // GroupName is the "group" that is needed to uniquely identify the API 26 | const GroupName = "controllermanager.config.k8s.io" 27 | 28 | // SchemeGroupVersion is group version used to register these objects 29 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} 30 | 31 | var ( 32 | // SchemeBuilder is the scheme builder with scheme init functions to run for this API package 33 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 34 | // localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package, 35 | // defaulting and conversion init funcs are registered as well. 36 | localSchemeBuilder = &SchemeBuilder 37 | // AddToScheme is a global function that registers this API group & version to a scheme 38 | AddToScheme = localSchemeBuilder.AddToScheme 39 | ) 40 | 41 | // Adds the list of known types to the given scheme. 42 | func addKnownTypes(scheme *runtime.Scheme) error { 43 | scheme.AddKnownTypes(SchemeGroupVersion, 44 | &LeaderMigrationConfiguration{}, 45 | ) 46 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /config/v1beta1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | ) 24 | 25 | // GroupName is the "group" that is needed to uniquely identify the API 26 | const GroupName = "controllermanager.config.k8s.io" 27 | 28 | // SchemeGroupVersion is group version used to register these objects 29 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} 30 | 31 | var ( 32 | // SchemeBuilder is the scheme builder with scheme init functions to run for this API package 33 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 34 | // localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package, 35 | // defaulting and conversion init funcs are registered as well. 36 | localSchemeBuilder = &SchemeBuilder 37 | // AddToScheme is a global function that registers this API group & version to a scheme 38 | AddToScheme = localSchemeBuilder.AddToScheme 39 | ) 40 | 41 | // Adds the list of known types to the given scheme. 42 | func addKnownTypes(scheme *runtime.Scheme) error { 43 | scheme.AddKnownTypes(SchemeGroupVersion, 44 | &LeaderMigrationConfiguration{}, 45 | ) 46 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /config/v1alpha1/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "time" 21 | 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1" 24 | ) 25 | 26 | func RecommendedDefaultGenericControllerManagerConfiguration(obj *GenericControllerManagerConfiguration) { 27 | zero := metav1.Duration{} 28 | if obj.Address == "" { 29 | obj.Address = "0.0.0.0" 30 | } 31 | if obj.MinResyncPeriod == zero { 32 | obj.MinResyncPeriod = metav1.Duration{Duration: 12 * time.Hour} 33 | } 34 | if obj.ControllerStartInterval == zero { 35 | obj.ControllerStartInterval = metav1.Duration{Duration: 0 * time.Second} 36 | } 37 | if len(obj.Controllers) == 0 { 38 | obj.Controllers = []string{"*"} 39 | } 40 | 41 | if len(obj.LeaderElection.ResourceLock) == 0 { 42 | // Use lease-based leader election to reduce cost. 43 | // We migrated for EndpointsLease lock in 1.17 and starting in 1.20 we 44 | // migrated to Lease lock. 45 | obj.LeaderElection.ResourceLock = "leases" 46 | } 47 | 48 | // Use the default ClientConnectionConfiguration and LeaderElectionConfiguration options 49 | componentbaseconfigv1alpha1.RecommendedDefaultClientConnectionConfiguration(&obj.ClientConnection) 50 | componentbaseconfigv1alpha1.RecommendedDefaultLeaderElectionConfiguration(&obj.LeaderElection) 51 | } 52 | -------------------------------------------------------------------------------- /config/v1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // LeaderMigrationConfiguration provides versioned configuration for all migrating leader locks. 24 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 25 | type LeaderMigrationConfiguration struct { 26 | metav1.TypeMeta `json:",inline"` 27 | 28 | // LeaderName is the name of the leader election resource that protects the migration 29 | // E.g. 1-20-KCM-to-1-21-CCM 30 | LeaderName string `json:"leaderName"` 31 | 32 | // ControllerLeaders contains a list of migrating leader lock configurations 33 | // +listType=atomic 34 | ControllerLeaders []ControllerLeaderConfiguration `json:"controllerLeaders"` 35 | } 36 | 37 | // ControllerLeaderConfiguration provides the configuration for a migrating leader lock. 38 | type ControllerLeaderConfiguration struct { 39 | // Name is the name of the controller being migrated 40 | // E.g. service-controller, route-controller, cloud-node-controller, etc 41 | Name string `json:"name"` 42 | 43 | // Component is the name of the component in which the controller should be running. 44 | // E.g. kube-controller-manager, cloud-controller-manager, etc 45 | // Or '*' meaning the controller can be run under any component that participates in the migration 46 | Component string `json:"component"` 47 | } 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: 4 | 5 | _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ 6 | 7 | ## How to become a contributor and submit your own code 8 | 9 | This repository does not directly accept contributions. Code in this repository is currently being copied from staging within the [Kubernetes repository](https://github.com/kubernetes/kubernetes/tree/master/staging). In order to contribute code to this repository, the coder must modify kubectl code in [staging](https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/controller-manager). 10 | 11 | See [this doc](https://github.com/kubernetes/community/tree/master/sig-api-machinery) and [this doc](https://github.com/kubernetes/community/tree/master/sig-cloud-provider) for information about contributing. 12 | 13 | ## Getting Started 14 | 15 | We have full documentation on how to get started contributing here: 16 | 17 | - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests 18 | - [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing) 19 | - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers 20 | 21 | ## Mentorship 22 | 23 | - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ⚠️ **This is an automatically published [staged repository](https://git.k8s.io/kubernetes/staging#external-repository-staging-area) for Kubernetes**. 2 | > Contributions, including issues and pull requests, should be made to the main Kubernetes repository: [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes). 3 | > This repository is read-only for importing, and not used for direct contributions. 4 | > See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. 5 | 6 | # Controller-manager 7 | 8 | ## Purpose 9 | 10 | This library contains common code for controller managers. Principally its for 11 | the Kube-Controller-Manager and Cloud-Controller-Manager. However other 12 | controller managers are welcome to use this code. 13 | 14 | 15 | ## Compatibility 16 | 17 | There are *NO compatibility guarantees* for this repository, yet. It is in direct support of Kubernetes, so branches 18 | will track Kubernetes and be compatible with that repo. As we more cleanly separate the layers, we will review the 19 | compatibility guarantee. We have a goal to make this easier to use in the future. 20 | 21 | 22 | ## Where does it come from? 23 | 24 | This package comes from the common code between kube-controller-manager and 25 | cloud-controller-manager. The intent is for it to contain our current 26 | understanding of the right way to build a controller manager. There are legacy 27 | aspects of these controller managers which should be cleaned before adding them 28 | here. 29 | `controller-manager` is synced from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/controller-manager. 30 | Code changes are made in that location, merged into `k8s.io/kubernetes` and later synced here. 31 | 32 | 33 | ## Things you should *NOT* do 34 | 35 | 1. Directly modify any files under `pkg` in this repo. Those are driven from `k8s.io/kubernetes/staging/src/k8s.io/controller-manager`. 36 | 2. Expect compatibility. This repo is currently changing rapidly in direct support of 37 | Kubernetes and the controller-manager processes and the cloud provider 38 | extraction effort. 39 | 40 | -------------------------------------------------------------------------------- /controller/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "net/http" 21 | 22 | "k8s.io/controller-manager/pkg/healthz" 23 | ) 24 | 25 | // Interface defines the base of a controller managed by a controller manager 26 | type Interface interface { 27 | // Name returns the canonical name of the controller. 28 | Name() string 29 | } 30 | 31 | // Debuggable defines a controller that allows the controller manager 32 | // to expose a debugging handler for the controller 33 | // 34 | // If a controller implements Debuggable, and the returned handler is 35 | // not nil, the controller manager can mount the handler during startup. 36 | type Debuggable interface { 37 | // DebuggingHandler returns a Handler that expose debugging information 38 | // for the controller, or nil if a debugging handler is not desired. 39 | // 40 | // The handler will be accessible at "/debug/controllers/{controllerName}/". 41 | DebuggingHandler() http.Handler 42 | } 43 | 44 | // HealthCheckable defines a controller that allows the controller manager 45 | // to include it in the health checks. 46 | // 47 | // If a controller implements HealthCheckable, and the returned check 48 | // is not nil, the controller manager can expose the check to the 49 | // /healthz endpoint. 50 | type HealthCheckable interface { 51 | // HealthChecker returns a UnnamedHealthChecker that the controller manager 52 | // can choose to mount on the /healthz endpoint, or nil if no custom 53 | // health check is desired. 54 | HealthChecker() healthz.UnnamedHealthChecker 55 | } 56 | -------------------------------------------------------------------------------- /config/v1beta1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // LeaderMigrationConfiguration provides versioned configuration for all migrating leader locks. 24 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 25 | type LeaderMigrationConfiguration struct { 26 | metav1.TypeMeta `json:",inline"` 27 | 28 | // LeaderName is the name of the leader election resource that protects the migration 29 | // E.g. 1-20-KCM-to-1-21-CCM 30 | LeaderName string `json:"leaderName"` 31 | 32 | // ResourceLock indicates the resource object type that will be used to lock 33 | // Should be "leases" or "endpoints" 34 | ResourceLock string `json:"resourceLock"` 35 | 36 | // ControllerLeaders contains a list of migrating leader lock configurations 37 | // +listType=atomic 38 | ControllerLeaders []ControllerLeaderConfiguration `json:"controllerLeaders"` 39 | } 40 | 41 | // ControllerLeaderConfiguration provides the configuration for a migrating leader lock. 42 | type ControllerLeaderConfiguration struct { 43 | // Name is the name of the controller being migrated 44 | // E.g. service-controller, route-controller, cloud-node-controller, etc 45 | Name string `json:"name"` 46 | 47 | // Component is the name of the component in which the controller should be running. 48 | // E.g. kube-controller-manager, cloud-controller-manager, etc 49 | // Or '*' meaning the controller can be run under any component that participates in the migration 50 | Component string `json:"component"` 51 | } 52 | -------------------------------------------------------------------------------- /pkg/informerfactory/informer_factory.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package informerfactory 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/runtime/schema" 21 | "k8s.io/client-go/informers" 22 | "k8s.io/client-go/metadata/metadatainformer" 23 | ) 24 | 25 | // InformerFactory creates informers for each group version resource. 26 | type InformerFactory interface { 27 | ForResource(resource schema.GroupVersionResource) (informers.GenericInformer, error) 28 | Start(stopCh <-chan struct{}) 29 | } 30 | 31 | type informerFactory struct { 32 | typedInformerFactory informers.SharedInformerFactory 33 | metadataInformerFactory metadatainformer.SharedInformerFactory 34 | } 35 | 36 | func (i *informerFactory) ForResource(resource schema.GroupVersionResource) (informers.GenericInformer, error) { 37 | informer, err := i.typedInformerFactory.ForResource(resource) 38 | if err != nil { 39 | return i.metadataInformerFactory.ForResource(resource), nil 40 | } 41 | return informer, nil 42 | } 43 | 44 | func (i *informerFactory) Start(stopCh <-chan struct{}) { 45 | i.typedInformerFactory.Start(stopCh) 46 | i.metadataInformerFactory.Start(stopCh) 47 | } 48 | 49 | // NewInformerFactory creates a new InformerFactory which works with both typed 50 | // resources and metadata-only resources 51 | func NewInformerFactory(typedInformerFactory informers.SharedInformerFactory, metadataInformerFactory metadatainformer.SharedInformerFactory) InformerFactory { 52 | return &informerFactory{ 53 | typedInformerFactory: typedInformerFactory, 54 | metadataInformerFactory: metadataInformerFactory, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /config/v1alpha1/conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/conversion" 21 | cmconfig "k8s.io/controller-manager/config" 22 | ) 23 | 24 | // Important! The public back-and-forth conversion functions for the types in this generic 25 | // package with ComponentConfig types need to be manually exposed like this in order for 26 | // other packages that reference this package to be able to call these conversion functions 27 | // in an autogenerated manner. 28 | // TODO: Fix the bug in conversion-gen so it automatically discovers these Convert_* functions 29 | // in autogenerated code as well. 30 | 31 | // Convert_v1alpha1_GenericControllerManagerConfiguration_To_config_GenericControllerManagerConfiguration is an autogenerated conversion function. 32 | func Convert_v1alpha1_GenericControllerManagerConfiguration_To_config_GenericControllerManagerConfiguration(in *GenericControllerManagerConfiguration, out *cmconfig.GenericControllerManagerConfiguration, s conversion.Scope) error { 33 | return autoConvert_v1alpha1_GenericControllerManagerConfiguration_To_config_GenericControllerManagerConfiguration(in, out, s) 34 | } 35 | 36 | // Convert_config_GenericControllerManagerConfiguration_To_v1alpha1_GenericControllerManagerConfiguration is an autogenerated conversion function. 37 | func Convert_config_GenericControllerManagerConfiguration_To_v1alpha1_GenericControllerManagerConfiguration(in *cmconfig.GenericControllerManagerConfiguration, out *GenericControllerManagerConfiguration, s conversion.Scope) error { 38 | return autoConvert_config_GenericControllerManagerConfiguration_To_v1alpha1_GenericControllerManagerConfiguration(in, out, s) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/healthz/handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package healthz 18 | 19 | import ( 20 | "net/http" 21 | "sync" 22 | 23 | "k8s.io/apiserver/pkg/server/healthz" 24 | "k8s.io/apiserver/pkg/server/mux" 25 | ) 26 | 27 | // MutableHealthzHandler returns a http.Handler that handles "/healthz" 28 | // following the standard healthz mechanism. 29 | // 30 | // This handler can register health checks after its creation, which 31 | // is originally not allowed with standard healthz handler. 32 | type MutableHealthzHandler struct { 33 | // handler is the underlying handler that will be replaced every time 34 | // new checks are added. 35 | handler http.Handler 36 | // mutex is a RWMutex that allows concurrent health checks (read) 37 | // but disallow replacing the handler at the same time (write). 38 | mutex sync.RWMutex 39 | checks []healthz.HealthChecker 40 | } 41 | 42 | func (h *MutableHealthzHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 43 | h.mutex.RLock() 44 | defer h.mutex.RUnlock() 45 | 46 | h.handler.ServeHTTP(writer, request) 47 | } 48 | 49 | // AddHealthChecker adds health check(s) to the handler. 50 | // 51 | // Every time this function is called, the handler have to be re-initiated. 52 | // It is advised to add as many checks at once as possible. 53 | func (h *MutableHealthzHandler) AddHealthChecker(checks ...healthz.HealthChecker) { 54 | h.mutex.Lock() 55 | defer h.mutex.Unlock() 56 | 57 | h.checks = append(h.checks, checks...) 58 | newMux := mux.NewPathRecorderMux("healthz") 59 | healthz.InstallHandler(newMux, h.checks...) 60 | h.handler = newMux 61 | } 62 | 63 | func NewMutableHealthzHandler(checks ...healthz.HealthChecker) *MutableHealthzHandler { 64 | h := &MutableHealthzHandler{} 65 | h.AddHealthChecker(checks...) 66 | 67 | return h 68 | } 69 | -------------------------------------------------------------------------------- /pkg/features/kube_features.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package features 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/util/version" 21 | "k8s.io/component-base/featuregate" 22 | ) 23 | 24 | // Every feature gate should have an entry here following this template: 25 | // 26 | // // owner: @username 27 | // MyFeature featuregate.Feature = "MyFeature" 28 | // 29 | // Feature gates should be listed in alphabetical, case-sensitive 30 | // (upper before any lower case character) order. This reduces the risk 31 | // of code conflicts because changes are more likely to be scattered 32 | // across the file. 33 | const ( 34 | // owner: @lukasmetzner 35 | // kep: http://kep.k8s.io/5237 36 | // Use watch based route controller reconciliation instead of frequent periodic reconciliation. 37 | CloudControllerManagerWatchBasedRoutesReconciliation featuregate.Feature = "CloudControllerManagerWatchBasedRoutesReconciliation" 38 | 39 | // owner: @nckturner 40 | // kep: http://kep.k8s.io/2699 41 | // Enable webhook in cloud controller manager 42 | CloudControllerManagerWebhook featuregate.Feature = "CloudControllerManagerWebhook" 43 | ) 44 | 45 | func SetupCurrentKubernetesSpecificFeatureGates(featuregates featuregate.MutableVersionedFeatureGate) error { 46 | return featuregates.AddVersioned(versionedCloudPublicFeatureGates) 47 | } 48 | 49 | // versionedCloudPublicFeatureGates consists of versioned cloud-specific feature keys. 50 | // To add a new feature, define a key for it above and add it here. 51 | var versionedCloudPublicFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{ 52 | CloudControllerManagerWatchBasedRoutesReconciliation: { 53 | {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Alpha}, 54 | }, 55 | CloudControllerManagerWebhook: { 56 | {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha}, 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /options/debugging.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "github.com/spf13/pflag" 21 | 22 | componentbaseconfig "k8s.io/component-base/config" 23 | ) 24 | 25 | // DebuggingOptions holds the Debugging options. 26 | type DebuggingOptions struct { 27 | *componentbaseconfig.DebuggingConfiguration 28 | } 29 | 30 | // RecommendedDebuggingOptions returns the currently recommended debugging options. These are subject to change 31 | // between releases as we add options and decide which features should be exposed or not by default. 32 | func RecommendedDebuggingOptions() *DebuggingOptions { 33 | return &DebuggingOptions{ 34 | DebuggingConfiguration: &componentbaseconfig.DebuggingConfiguration{ 35 | EnableProfiling: true, // profile debugging is cheap to have exposed and standard on kube binaries 36 | }, 37 | } 38 | } 39 | 40 | // AddFlags adds flags related to debugging for controller manager to the specified FlagSet. 41 | func (o *DebuggingOptions) AddFlags(fs *pflag.FlagSet) { 42 | if o == nil { 43 | return 44 | } 45 | 46 | fs.BoolVar(&o.EnableProfiling, "profiling", o.EnableProfiling, 47 | "Enable profiling via web interface host:port/debug/pprof/") 48 | fs.BoolVar(&o.EnableContentionProfiling, "contention-profiling", o.EnableContentionProfiling, 49 | "Enable block profiling, if profiling is enabled") 50 | } 51 | 52 | // ApplyTo fills up Debugging config with options. 53 | func (o *DebuggingOptions) ApplyTo(cfg *componentbaseconfig.DebuggingConfiguration) error { 54 | if o == nil { 55 | return nil 56 | } 57 | 58 | cfg.EnableProfiling = o.EnableProfiling 59 | cfg.EnableContentionProfiling = o.EnableContentionProfiling 60 | 61 | return nil 62 | } 63 | 64 | // Validate checks validation of DebuggingOptions. 65 | func (o *DebuggingOptions) Validate() []error { 66 | if o == nil { 67 | return nil 68 | } 69 | 70 | errs := []error{} 71 | return errs 72 | } 73 | -------------------------------------------------------------------------------- /app/helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package app 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | "time" 24 | 25 | "k8s.io/apimachinery/pkg/util/sets" 26 | "k8s.io/apimachinery/pkg/util/wait" 27 | clientset "k8s.io/client-go/kubernetes" 28 | "k8s.io/klog/v2" 29 | ) 30 | 31 | // WaitForAPIServer waits for the API Server's /healthz endpoint to report "ok" with timeout. 32 | func WaitForAPIServer(client clientset.Interface, timeout time.Duration) error { 33 | var lastErr error 34 | 35 | err := wait.PollImmediate(time.Second, timeout, func() (bool, error) { 36 | healthStatus := 0 37 | result := client.Discovery().RESTClient().Get().AbsPath("/healthz").Do(context.TODO()).StatusCode(&healthStatus) 38 | if result.Error() != nil { 39 | lastErr = fmt.Errorf("failed to get apiserver /healthz status: %v", result.Error()) 40 | return false, nil 41 | } 42 | if healthStatus != http.StatusOK { 43 | content, _ := result.Raw() 44 | lastErr = fmt.Errorf("APIServer isn't healthy: %v", string(content)) 45 | klog.Warningf("APIServer isn't healthy yet: %v. Waiting a little while.", string(content)) 46 | return false, nil 47 | } 48 | 49 | return true, nil 50 | }) 51 | 52 | if err != nil { 53 | return fmt.Errorf("%v: %v", err, lastErr) 54 | } 55 | 56 | return nil 57 | } 58 | 59 | // IsControllerEnabled check if a specified controller enabled or not. 60 | func IsControllerEnabled(name string, disabledByDefaultControllers sets.String, controllers []string) bool { 61 | hasStar := false 62 | for _, ctrl := range controllers { 63 | if ctrl == name { 64 | return true 65 | } 66 | if ctrl == "-"+name { 67 | return false 68 | } 69 | if ctrl == "*" { 70 | hasStar = true 71 | } 72 | } 73 | // if we get here, there was no explicit choice 74 | if !hasStar { 75 | // nothing on by default 76 | return false 77 | } 78 | 79 | return !disabledByDefaultControllers.Has(name) 80 | } 81 | -------------------------------------------------------------------------------- /config/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by deepcopy-gen. DO NOT EDIT. 21 | 22 | package v1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *ControllerLeaderConfiguration) DeepCopyInto(out *ControllerLeaderConfiguration) { 30 | *out = *in 31 | return 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerLeaderConfiguration. 35 | func (in *ControllerLeaderConfiguration) DeepCopy() *ControllerLeaderConfiguration { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(ControllerLeaderConfiguration) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 45 | func (in *LeaderMigrationConfiguration) DeepCopyInto(out *LeaderMigrationConfiguration) { 46 | *out = *in 47 | out.TypeMeta = in.TypeMeta 48 | if in.ControllerLeaders != nil { 49 | in, out := &in.ControllerLeaders, &out.ControllerLeaders 50 | *out = make([]ControllerLeaderConfiguration, len(*in)) 51 | copy(*out, *in) 52 | } 53 | return 54 | } 55 | 56 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderMigrationConfiguration. 57 | func (in *LeaderMigrationConfiguration) DeepCopy() *LeaderMigrationConfiguration { 58 | if in == nil { 59 | return nil 60 | } 61 | out := new(LeaderMigrationConfiguration) 62 | in.DeepCopyInto(out) 63 | return out 64 | } 65 | 66 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 67 | func (in *LeaderMigrationConfiguration) DeepCopyObject() runtime.Object { 68 | if c := in.DeepCopy(); c != nil { 69 | return c 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/leadermigration/migrator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package leadermigration 18 | 19 | import ( 20 | internal "k8s.io/controller-manager/config" 21 | ) 22 | 23 | // LeaderMigrator holds information required by the leader migration process. 24 | type LeaderMigrator struct { 25 | // MigrationReady is closed after the controller manager finishes preparing for the migration lock. 26 | // After this point, the leader migration process will proceed to acquire the migration lock. 27 | MigrationReady chan struct{} 28 | 29 | // FilterFunc returns a FilterResult telling the controller manager what to do with the controller. 30 | FilterFunc FilterFunc 31 | } 32 | 33 | // NewLeaderMigrator creates a LeaderMigrator with given config for the given component. component 34 | // indicates which controller manager is requesting this leader migration, and it should be consistent 35 | // with the component field of ControllerLeaderConfiguration. 36 | func NewLeaderMigrator(config *internal.LeaderMigrationConfiguration, component string) *LeaderMigrator { 37 | migratedControllers := make(map[string]bool) 38 | for _, leader := range config.ControllerLeaders { 39 | migratedControllers[leader.Name] = leader.Component == component || leader.Component == "*" 40 | } 41 | return &LeaderMigrator{ 42 | MigrationReady: make(chan struct{}), 43 | FilterFunc: func(controllerName string) FilterResult { 44 | shouldRun, ok := migratedControllers[controllerName] 45 | if ok { 46 | // The controller is included in the migration 47 | if shouldRun { 48 | // If the controller manager should run the controller, 49 | // start it in the migration lock. 50 | return ControllerMigrated 51 | } 52 | // Otherwise, the controller should be started by 53 | // some other controller manager. 54 | return ControllerUnowned 55 | } 56 | // The controller is not included in the migration, 57 | // and should be started in the main lock. 58 | return ControllerNonMigrated 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /config/v1beta1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by deepcopy-gen. DO NOT EDIT. 21 | 22 | package v1beta1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *ControllerLeaderConfiguration) DeepCopyInto(out *ControllerLeaderConfiguration) { 30 | *out = *in 31 | return 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerLeaderConfiguration. 35 | func (in *ControllerLeaderConfiguration) DeepCopy() *ControllerLeaderConfiguration { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(ControllerLeaderConfiguration) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 45 | func (in *LeaderMigrationConfiguration) DeepCopyInto(out *LeaderMigrationConfiguration) { 46 | *out = *in 47 | out.TypeMeta = in.TypeMeta 48 | if in.ControllerLeaders != nil { 49 | in, out := &in.ControllerLeaders, &out.ControllerLeaders 50 | *out = make([]ControllerLeaderConfiguration, len(*in)) 51 | copy(*out, *in) 52 | } 53 | return 54 | } 55 | 56 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderMigrationConfiguration. 57 | func (in *LeaderMigrationConfiguration) DeepCopy() *LeaderMigrationConfiguration { 58 | if in == nil { 59 | return nil 60 | } 61 | out := new(LeaderMigrationConfiguration) 62 | in.DeepCopyInto(out) 63 | return out 64 | } 65 | 66 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 67 | func (in *LeaderMigrationConfiguration) DeepCopyObject() runtime.Object { 68 | if c := in.DeepCopy(); c != nil { 69 | return c 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /app/controllercontext.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package app 18 | 19 | import ( 20 | "time" 21 | 22 | "k8s.io/client-go/informers" 23 | "k8s.io/client-go/restmapper" 24 | controllersmetrics "k8s.io/component-base/metrics/prometheus/controllers" 25 | "k8s.io/controller-manager/pkg/clientbuilder" 26 | "k8s.io/controller-manager/pkg/informerfactory" 27 | ) 28 | 29 | // ControllerContext defines the context object for controller 30 | type ControllerContext struct { 31 | // ClientBuilder will provide a client for this controller to use 32 | ClientBuilder clientbuilder.ControllerClientBuilder 33 | 34 | // InformerFactory gives access to informers for the controller. 35 | InformerFactory informers.SharedInformerFactory 36 | 37 | // ObjectOrMetadataInformerFactory gives access to informers for typed resources 38 | // and dynamic resources by their metadata. All generic controllers currently use 39 | // object metadata - if a future controller needs access to the full object this 40 | // would become GenericInformerFactory and take a dynamic client. 41 | ObjectOrMetadataInformerFactory informerfactory.InformerFactory 42 | 43 | // DeferredDiscoveryRESTMapper is a RESTMapper that will defer 44 | // initialization of the RESTMapper until the first mapping is 45 | // requested. 46 | RESTMapper *restmapper.DeferredDiscoveryRESTMapper 47 | 48 | // Stop is the stop channel 49 | Stop <-chan struct{} 50 | 51 | // InformersStarted is closed after all of the controllers have been initialized and are running. After this point it is safe, 52 | // for an individual controller to start the shared informers. Before it is closed, they should not. 53 | InformersStarted chan struct{} 54 | 55 | // ResyncPeriod generates a duration each time it is invoked; this is so that 56 | // multiple controllers don't get into lock-step and all hammer the apiserver 57 | // with list requests simultaneously. 58 | ResyncPeriod func() time.Duration 59 | 60 | // ControllerManagerMetrics provides a proxy to set controller manager specific metrics. 61 | ControllerManagerMetrics *controllersmetrics.ControllerManagerMetrics 62 | } 63 | -------------------------------------------------------------------------------- /app/serve.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package app 18 | 19 | import ( 20 | "net/http" 21 | goruntime "runtime" 22 | 23 | genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" 24 | apirequest "k8s.io/apiserver/pkg/endpoints/request" 25 | apiserver "k8s.io/apiserver/pkg/server" 26 | genericfilters "k8s.io/apiserver/pkg/server/filters" 27 | "k8s.io/apiserver/pkg/server/mux" 28 | "k8s.io/apiserver/pkg/server/routes" 29 | "k8s.io/client-go/kubernetes/scheme" 30 | componentbaseconfig "k8s.io/component-base/config" 31 | "k8s.io/component-base/configz" 32 | "k8s.io/component-base/logs" 33 | "k8s.io/component-base/metrics/legacyregistry" 34 | _ "k8s.io/component-base/metrics/prometheus/workqueue" // for workqueue metric registration 35 | ) 36 | 37 | // BuildHandlerChain builds a handler chain with a base handler and CompletedConfig. 38 | func BuildHandlerChain(apiHandler http.Handler, authorizationInfo *apiserver.AuthorizationInfo, authenticationInfo *apiserver.AuthenticationInfo) http.Handler { 39 | requestInfoResolver := &apirequest.RequestInfoFactory{} 40 | failedHandler := genericapifilters.Unauthorized(scheme.Codecs) 41 | 42 | handler := apiHandler 43 | if authorizationInfo != nil { 44 | handler = genericapifilters.WithAuthorization(apiHandler, authorizationInfo.Authorizer, scheme.Codecs) 45 | } 46 | if authenticationInfo != nil { 47 | handler = genericapifilters.WithAuthentication(handler, authenticationInfo.Authenticator, failedHandler, nil, nil) 48 | } 49 | handler = genericapifilters.WithRequestInfo(handler, requestInfoResolver) 50 | handler = genericapifilters.WithCacheControl(handler) 51 | handler = genericfilters.WithHTTPLogging(handler) 52 | handler = genericfilters.WithPanicRecovery(handler, requestInfoResolver) 53 | 54 | return handler 55 | } 56 | 57 | // NewBaseHandler takes in CompletedConfig and returns a handler. 58 | func NewBaseHandler(c *componentbaseconfig.DebuggingConfiguration, healthzHandler http.Handler) *mux.PathRecorderMux { 59 | mux := mux.NewPathRecorderMux("controller-manager") 60 | mux.Handle("/healthz", healthzHandler) 61 | if c.EnableProfiling { 62 | routes.Profiling{}.Install(mux) 63 | if c.EnableContentionProfiling { 64 | goruntime.SetBlockProfileRate(1) 65 | } 66 | routes.DebugFlags{}.Install(mux, "v", routes.StringFlagPutHandler(logs.GlogSetter)) 67 | } 68 | configz.InstallHandler(mux) 69 | mux.Handle("/metrics", legacyregistry.Handler()) 70 | 71 | return mux 72 | } 73 | -------------------------------------------------------------------------------- /app/helper_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package app 18 | 19 | import ( 20 | "testing" 21 | 22 | "k8s.io/apimachinery/pkg/util/sets" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestIsControllerEnabled(t *testing.T) { 28 | tcs := []struct { 29 | name string 30 | controllerName string 31 | controllers []string 32 | disabledByDefaultControllers []string 33 | expected bool 34 | }{ 35 | { 36 | name: "on by name", 37 | controllerName: "bravo", 38 | controllers: []string{"alpha", "bravo", "-charlie"}, 39 | disabledByDefaultControllers: []string{"delta", "echo"}, 40 | expected: true, 41 | }, 42 | { 43 | name: "off by name", 44 | controllerName: "charlie", 45 | controllers: []string{"alpha", "bravo", "-charlie"}, 46 | disabledByDefaultControllers: []string{"delta", "echo"}, 47 | expected: false, 48 | }, 49 | { 50 | name: "on by default", 51 | controllerName: "alpha", 52 | controllers: []string{"*"}, 53 | disabledByDefaultControllers: []string{"delta", "echo"}, 54 | expected: true, 55 | }, 56 | { 57 | name: "off by default", 58 | controllerName: "delta", 59 | controllers: []string{"*"}, 60 | disabledByDefaultControllers: []string{"delta", "echo"}, 61 | expected: false, 62 | }, 63 | { 64 | name: "on by star, not off by name", 65 | controllerName: "alpha", 66 | controllers: []string{"*", "-charlie"}, 67 | disabledByDefaultControllers: []string{"delta", "echo"}, 68 | expected: true, 69 | }, 70 | { 71 | name: "off by name with star", 72 | controllerName: "charlie", 73 | controllers: []string{"*", "-charlie"}, 74 | disabledByDefaultControllers: []string{"delta", "echo"}, 75 | expected: false, 76 | }, 77 | { 78 | name: "off by default implicit, no star", 79 | controllerName: "foxtrot", 80 | controllers: []string{"alpha", "bravo", "-charlie"}, 81 | disabledByDefaultControllers: []string{"delta", "echo"}, 82 | expected: false, 83 | }, 84 | } 85 | 86 | for _, tc := range tcs { 87 | actual := IsControllerEnabled(tc.controllerName, sets.NewString(tc.disabledByDefaultControllers...), tc.controllers) 88 | assert.Equal(t, tc.expected, actual, "%v: expected %v, got %v", tc.name, tc.expected, actual) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /pkg/leadermigration/options/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/pflag" 23 | "k8s.io/controller-manager/config" 24 | migrationconfig "k8s.io/controller-manager/pkg/leadermigration/config" 25 | ) 26 | 27 | // LeaderMigrationOptions is the set of options for Leader Migration, 28 | // which is given to the controller manager through flags 29 | type LeaderMigrationOptions struct { 30 | // Enabled indicates whether leader migration is enabled through the --enabled-leader-migration flag. 31 | Enabled bool 32 | 33 | // ControllerMigrationConfig is the path to the file of LeaderMigrationConfiguration type. 34 | // It can be set with --leader-migration-config flag 35 | // If the path is "" (default vaule), the default vaule will be used. 36 | ControllerMigrationConfig string 37 | } 38 | 39 | // DefaultLeaderMigrationOptions returns a LeaderMigrationOptions with default values. 40 | func DefaultLeaderMigrationOptions() *LeaderMigrationOptions { 41 | return &LeaderMigrationOptions{ 42 | Enabled: false, 43 | ControllerMigrationConfig: "", 44 | } 45 | } 46 | 47 | // AddFlags adds all flags related to leader migration to given flag set. 48 | func (o *LeaderMigrationOptions) AddFlags(fs *pflag.FlagSet) { 49 | if o == nil { 50 | return 51 | } 52 | fs.BoolVar(&o.Enabled, "enable-leader-migration", false, "Whether to enable controller leader migration.") 53 | fs.StringVar(&o.ControllerMigrationConfig, "leader-migration-config", "", 54 | "Path to the config file for controller leader migration, "+ 55 | "or empty to use the value that reflects default configuration of the controller manager. "+ 56 | "The config file should be of type LeaderMigrationConfiguration, group controllermanager.config.k8s.io, version v1alpha1.") 57 | } 58 | 59 | // ApplyTo applies the options of leader migration to generic configuration. 60 | func (o *LeaderMigrationOptions) ApplyTo(cfg *config.GenericControllerManagerConfiguration) error { 61 | if o == nil { 62 | // an nil LeaderMigrationOptions indicates that default options should be used 63 | // in which case leader migration will be disabled 64 | cfg.LeaderMigrationEnabled = false 65 | return nil 66 | } 67 | cfg.LeaderMigrationEnabled = o.Enabled 68 | if !cfg.LeaderMigrationEnabled { 69 | return nil 70 | } 71 | if o.ControllerMigrationConfig == "" { 72 | cfg.LeaderMigration = *migrationconfig.DefaultLeaderMigrationConfiguration() 73 | return nil 74 | } 75 | leaderMigrationConfig, err := migrationconfig.ReadLeaderMigrationConfiguration(o.ControllerMigrationConfig) 76 | if err != nil { 77 | return err 78 | } 79 | errs := migrationconfig.ValidateLeaderMigrationConfiguration(leaderMigrationConfig) 80 | if len(errs) != 0 { 81 | return fmt.Errorf("failed to parse leader migration configuration: %v", errs) 82 | } 83 | cfg.LeaderMigration = *leaderMigrationConfig 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /config/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by deepcopy-gen. DO NOT EDIT. 21 | 22 | package config 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *ControllerLeaderConfiguration) DeepCopyInto(out *ControllerLeaderConfiguration) { 30 | *out = *in 31 | return 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerLeaderConfiguration. 35 | func (in *ControllerLeaderConfiguration) DeepCopy() *ControllerLeaderConfiguration { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(ControllerLeaderConfiguration) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 45 | func (in *GenericControllerManagerConfiguration) DeepCopyInto(out *GenericControllerManagerConfiguration) { 46 | *out = *in 47 | out.MinResyncPeriod = in.MinResyncPeriod 48 | out.ClientConnection = in.ClientConnection 49 | out.ControllerStartInterval = in.ControllerStartInterval 50 | out.LeaderElection = in.LeaderElection 51 | if in.Controllers != nil { 52 | in, out := &in.Controllers, &out.Controllers 53 | *out = make([]string, len(*in)) 54 | copy(*out, *in) 55 | } 56 | out.Debugging = in.Debugging 57 | in.LeaderMigration.DeepCopyInto(&out.LeaderMigration) 58 | return 59 | } 60 | 61 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericControllerManagerConfiguration. 62 | func (in *GenericControllerManagerConfiguration) DeepCopy() *GenericControllerManagerConfiguration { 63 | if in == nil { 64 | return nil 65 | } 66 | out := new(GenericControllerManagerConfiguration) 67 | in.DeepCopyInto(out) 68 | return out 69 | } 70 | 71 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 72 | func (in *LeaderMigrationConfiguration) DeepCopyInto(out *LeaderMigrationConfiguration) { 73 | *out = *in 74 | out.TypeMeta = in.TypeMeta 75 | if in.ControllerLeaders != nil { 76 | in, out := &in.ControllerLeaders, &out.ControllerLeaders 77 | *out = make([]ControllerLeaderConfiguration, len(*in)) 78 | copy(*out, *in) 79 | } 80 | return 81 | } 82 | 83 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderMigrationConfiguration. 84 | func (in *LeaderMigrationConfiguration) DeepCopy() *LeaderMigrationConfiguration { 85 | if in == nil { 86 | return nil 87 | } 88 | out := new(LeaderMigrationConfiguration) 89 | in.DeepCopyInto(out) 90 | return out 91 | } 92 | 93 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 94 | func (in *LeaderMigrationConfiguration) DeepCopyObject() runtime.Object { 95 | if c := in.DeepCopy(); c != nil { 96 | return c 97 | } 98 | return nil 99 | } 100 | -------------------------------------------------------------------------------- /config/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package config should only include generic configurations 18 | package config 19 | 20 | import ( 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | componentbaseconfig "k8s.io/component-base/config" 23 | ) 24 | 25 | // GenericControllerManagerConfiguration holds configuration for a generic controller-manager 26 | type GenericControllerManagerConfiguration struct { 27 | // port is the port that the controller-manager's http service runs on. 28 | Port int32 29 | // address is the IP address to serve on (set to 0.0.0.0 for all interfaces). 30 | Address string 31 | // minResyncPeriod is the resync period in reflectors; will be random between 32 | // minResyncPeriod and 2*minResyncPeriod. 33 | MinResyncPeriod metav1.Duration 34 | // ClientConnection specifies the kubeconfig file and client connection 35 | // settings for the proxy server to use when communicating with the apiserver. 36 | ClientConnection componentbaseconfig.ClientConnectionConfiguration 37 | // How long to wait between starting controller managers 38 | ControllerStartInterval metav1.Duration 39 | // leaderElection defines the configuration of leader election client. 40 | LeaderElection componentbaseconfig.LeaderElectionConfiguration 41 | // Controllers is the list of controllers to enable or disable 42 | // '*' means "all enabled by default controllers" 43 | // 'foo' means "enable 'foo'" 44 | // '-foo' means "disable 'foo'" 45 | // first item for a particular name wins 46 | Controllers []string 47 | // DebuggingConfiguration holds configuration for Debugging related features. 48 | Debugging componentbaseconfig.DebuggingConfiguration 49 | // LeaderMigrationEnabled indicates whether Leader Migration should be enabled for the controller manager. 50 | LeaderMigrationEnabled bool 51 | // LeaderMigration holds the configuration for Leader Migration. 52 | LeaderMigration LeaderMigrationConfiguration 53 | } 54 | 55 | // LeaderMigrationConfiguration provides versioned configuration for all migrating leader locks. 56 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 57 | type LeaderMigrationConfiguration struct { 58 | metav1.TypeMeta 59 | 60 | // LeaderName is the name of the leader election resource that protects the migration 61 | // E.g. 1-20-KCM-to-1-21-CCM 62 | LeaderName string 63 | 64 | // ResourceLock indicates the resource object type that will be used to lock 65 | // Should be "leases" or "endpoints" 66 | ResourceLock string 67 | 68 | // ControllerLeaders contains a list of migrating leader lock configurations 69 | ControllerLeaders []ControllerLeaderConfiguration 70 | } 71 | 72 | // ControllerLeaderConfiguration provides the configuration for a migrating leader lock. 73 | type ControllerLeaderConfiguration struct { 74 | // Name is the name of the controller being migrated 75 | // E.g. service-controller, route-controller, cloud-node-controller, etc 76 | Name string 77 | 78 | // Component is the name of the component in which the controller should be running. 79 | // E.g. kube-controller-manager, cloud-controller-manager, etc 80 | // Or '*' meaning the controller can be run under any component that participates in the migration 81 | Component string 82 | } 83 | -------------------------------------------------------------------------------- /config/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by deepcopy-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *ControllerLeaderConfiguration) DeepCopyInto(out *ControllerLeaderConfiguration) { 30 | *out = *in 31 | return 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerLeaderConfiguration. 35 | func (in *ControllerLeaderConfiguration) DeepCopy() *ControllerLeaderConfiguration { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(ControllerLeaderConfiguration) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 45 | func (in *GenericControllerManagerConfiguration) DeepCopyInto(out *GenericControllerManagerConfiguration) { 46 | *out = *in 47 | out.MinResyncPeriod = in.MinResyncPeriod 48 | out.ClientConnection = in.ClientConnection 49 | out.ControllerStartInterval = in.ControllerStartInterval 50 | in.LeaderElection.DeepCopyInto(&out.LeaderElection) 51 | if in.Controllers != nil { 52 | in, out := &in.Controllers, &out.Controllers 53 | *out = make([]string, len(*in)) 54 | copy(*out, *in) 55 | } 56 | in.Debugging.DeepCopyInto(&out.Debugging) 57 | in.LeaderMigration.DeepCopyInto(&out.LeaderMigration) 58 | return 59 | } 60 | 61 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericControllerManagerConfiguration. 62 | func (in *GenericControllerManagerConfiguration) DeepCopy() *GenericControllerManagerConfiguration { 63 | if in == nil { 64 | return nil 65 | } 66 | out := new(GenericControllerManagerConfiguration) 67 | in.DeepCopyInto(out) 68 | return out 69 | } 70 | 71 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 72 | func (in *LeaderMigrationConfiguration) DeepCopyInto(out *LeaderMigrationConfiguration) { 73 | *out = *in 74 | out.TypeMeta = in.TypeMeta 75 | if in.ControllerLeaders != nil { 76 | in, out := &in.ControllerLeaders, &out.ControllerLeaders 77 | *out = make([]ControllerLeaderConfiguration, len(*in)) 78 | copy(*out, *in) 79 | } 80 | return 81 | } 82 | 83 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderMigrationConfiguration. 84 | func (in *LeaderMigrationConfiguration) DeepCopy() *LeaderMigrationConfiguration { 85 | if in == nil { 86 | return nil 87 | } 88 | out := new(LeaderMigrationConfiguration) 89 | in.DeepCopyInto(out) 90 | return out 91 | } 92 | 93 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 94 | func (in *LeaderMigrationConfiguration) DeepCopyObject() runtime.Object { 95 | if c := in.DeepCopy(); c != nil { 96 | return c 97 | } 98 | return nil 99 | } 100 | -------------------------------------------------------------------------------- /config/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1" 22 | ) 23 | 24 | // GenericControllerManagerConfiguration holds configuration for a generic controller-manager. 25 | type GenericControllerManagerConfiguration struct { 26 | // port is the port that the controller-manager's http service runs on. 27 | Port int32 28 | // address is the IP address to serve on (set to 0.0.0.0 for all interfaces). 29 | Address string 30 | // minResyncPeriod is the resync period in reflectors; will be random between 31 | // minResyncPeriod and 2*minResyncPeriod. 32 | MinResyncPeriod metav1.Duration 33 | // ClientConnection specifies the kubeconfig file and client connection 34 | // settings for the proxy server to use when communicating with the apiserver. 35 | ClientConnection componentbaseconfigv1alpha1.ClientConnectionConfiguration 36 | // How long to wait between starting controller managers 37 | ControllerStartInterval metav1.Duration 38 | // leaderElection defines the configuration of leader election client. 39 | LeaderElection componentbaseconfigv1alpha1.LeaderElectionConfiguration 40 | // Controllers is the list of controllers to enable or disable 41 | // '*' means "all enabled by default controllers" 42 | // 'foo' means "enable 'foo'" 43 | // '-foo' means "disable 'foo'" 44 | // first item for a particular name wins 45 | Controllers []string 46 | // DebuggingConfiguration holds configuration for Debugging related features. 47 | Debugging componentbaseconfigv1alpha1.DebuggingConfiguration 48 | // LeaderMigrationEnabled indicates whether Leader Migration should be enabled for the controller manager. 49 | LeaderMigrationEnabled bool 50 | // LeaderMigration holds the configuration for Leader Migration. 51 | LeaderMigration LeaderMigrationConfiguration 52 | } 53 | 54 | // LeaderMigrationConfiguration provides versioned configuration for all migrating leader locks. 55 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 56 | type LeaderMigrationConfiguration struct { 57 | metav1.TypeMeta `json:",inline"` 58 | 59 | // LeaderName is the name of the leader election resource that protects the migration 60 | // E.g. 1-20-KCM-to-1-21-CCM 61 | LeaderName string `json:"leaderName"` 62 | 63 | // ResourceLock indicates the resource object type that will be used to lock 64 | // Should be "leases" or "endpoints" 65 | ResourceLock string `json:"resourceLock"` 66 | 67 | // ControllerLeaders contains a list of migrating leader lock configurations 68 | ControllerLeaders []ControllerLeaderConfiguration `json:"controllerLeaders"` 69 | } 70 | 71 | // ControllerLeaderConfiguration provides the configuration for a migrating leader lock. 72 | type ControllerLeaderConfiguration struct { 73 | // Name is the name of the controller being migrated 74 | // E.g. service-controller, route-controller, cloud-node-controller, etc 75 | Name string `json:"name"` 76 | 77 | // Component is the name of the component in which the controller should be running. 78 | // E.g. kube-controller-manager, cloud-controller-manager, etc 79 | // Or '*' meaning the controller can be run under any component that participates in the migration 80 | Component string `json:"component"` 81 | } 82 | -------------------------------------------------------------------------------- /config/v1/conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | "unsafe" 21 | 22 | "k8s.io/apimachinery/pkg/conversion" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/controller-manager/config" 25 | ) 26 | 27 | func init() { 28 | localSchemeBuilder.Register(RegisterConversions) 29 | } 30 | 31 | const ResourceLockLeases = "leases" 32 | 33 | // RegisterConversions adds conversion functions to the given scheme. 34 | // Public to allow building arbitrary schemes. 35 | func RegisterConversions(s *runtime.Scheme) error { 36 | if err := s.AddGeneratedConversionFunc((*ControllerLeaderConfiguration)(nil), (*config.ControllerLeaderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 37 | return Convert_v1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(a.(*ControllerLeaderConfiguration), b.(*config.ControllerLeaderConfiguration), scope) 38 | }); err != nil { 39 | return err 40 | } 41 | if err := s.AddGeneratedConversionFunc((*config.ControllerLeaderConfiguration)(nil), (*ControllerLeaderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 42 | return Convert_config_ControllerLeaderConfiguration_To_v1_ControllerLeaderConfiguration(a.(*config.ControllerLeaderConfiguration), b.(*ControllerLeaderConfiguration), scope) 43 | }); err != nil { 44 | return err 45 | } 46 | if err := s.AddGeneratedConversionFunc((*LeaderMigrationConfiguration)(nil), (*config.LeaderMigrationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 47 | return Convert_v1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(a.(*LeaderMigrationConfiguration), b.(*config.LeaderMigrationConfiguration), scope) 48 | }); err != nil { 49 | return err 50 | } 51 | if err := s.AddGeneratedConversionFunc((*config.LeaderMigrationConfiguration)(nil), (*LeaderMigrationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 52 | return Convert_config_LeaderMigrationConfiguration_To_v1_LeaderMigrationConfiguration(a.(*config.LeaderMigrationConfiguration), b.(*LeaderMigrationConfiguration), scope) 53 | }); err != nil { 54 | return err 55 | } 56 | return nil 57 | } 58 | 59 | func Convert_config_LeaderMigrationConfiguration_To_v1_LeaderMigrationConfiguration(in *config.LeaderMigrationConfiguration, out *LeaderMigrationConfiguration, s conversion.Scope) error { 60 | out.LeaderName = in.LeaderName 61 | out.ControllerLeaders = *(*[]ControllerLeaderConfiguration)(unsafe.Pointer(&in.ControllerLeaders)) 62 | return nil 63 | } 64 | 65 | func Convert_v1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(in *LeaderMigrationConfiguration, out *config.LeaderMigrationConfiguration, s conversion.Scope) error { 66 | out.LeaderName = in.LeaderName 67 | out.ControllerLeaders = *(*[]config.ControllerLeaderConfiguration)(unsafe.Pointer(&in.ControllerLeaders)) 68 | out.ResourceLock = ResourceLockLeases 69 | return nil 70 | } 71 | 72 | func Convert_v1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(in *ControllerLeaderConfiguration, out *config.ControllerLeaderConfiguration, s conversion.Scope) error { 73 | out.Name = in.Name 74 | out.Component = in.Component 75 | return nil 76 | } 77 | 78 | func Convert_config_ControllerLeaderConfiguration_To_v1_ControllerLeaderConfiguration(in *config.ControllerLeaderConfiguration, out *ControllerLeaderConfiguration, s conversion.Scope) error { 79 | out.Name = in.Name 80 | out.Component = in.Component 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /pkg/leadermigration/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/apimachinery/pkg/runtime/serializer" 25 | util "k8s.io/apimachinery/pkg/util/runtime" 26 | "k8s.io/apimachinery/pkg/util/validation/field" 27 | internal "k8s.io/controller-manager/config" 28 | v1 "k8s.io/controller-manager/config/v1" 29 | "k8s.io/controller-manager/config/v1alpha1" 30 | "k8s.io/controller-manager/config/v1beta1" 31 | ) 32 | 33 | // ResourceLockLeases is the resourceLock value for 'leases' API 34 | const ResourceLockLeases = "leases" 35 | 36 | // ResourceLockEndpoints is the resourceLock value for 'endpoints' API 37 | const ResourceLockEndpoints = "endpoints" 38 | 39 | var cfgScheme = runtime.NewScheme() 40 | 41 | func init() { 42 | // internal 43 | util.Must(internal.AddToScheme(cfgScheme)) 44 | 45 | // v1alpha1 46 | util.Must(v1alpha1.AddToScheme(cfgScheme)) 47 | util.Must(cfgScheme.SetVersionPriority(v1alpha1.SchemeGroupVersion)) 48 | 49 | // v1beta1 50 | util.Must(v1beta1.AddToScheme(cfgScheme)) 51 | util.Must(cfgScheme.SetVersionPriority(v1beta1.SchemeGroupVersion)) 52 | 53 | // v1 54 | util.Must(v1.AddToScheme(cfgScheme)) 55 | util.Must(cfgScheme.SetVersionPriority(v1.SchemeGroupVersion)) 56 | } 57 | 58 | // ReadLeaderMigrationConfiguration reads LeaderMigrationConfiguration from a YAML file at the given path. 59 | // The parsed LeaderMigrationConfiguration may be invalid. 60 | // It returns an error if the file did not exist. 61 | func ReadLeaderMigrationConfiguration(configFilePath string) (*internal.LeaderMigrationConfiguration, error) { 62 | data, err := os.ReadFile(configFilePath) 63 | if err != nil { 64 | return nil, fmt.Errorf("unable to read leader migration configuration from %q: %w", configFilePath, err) 65 | } 66 | config, gvk, err := serializer.NewCodecFactory(cfgScheme, serializer.EnableStrict).UniversalDecoder().Decode(data, nil, nil) 67 | if err != nil { 68 | return nil, err 69 | } 70 | internalConfig, ok := config.(*internal.LeaderMigrationConfiguration) 71 | if !ok { 72 | return nil, fmt.Errorf("unexpected config type: %v", gvk) 73 | } 74 | return internalConfig, nil 75 | } 76 | 77 | // ValidateLeaderMigrationConfiguration validates the LeaderMigrationConfiguration against common errors. 78 | // It checks required names and whether resourceLock is either 'leases' or 'endpoints'. 79 | // It will return nil if it does not find anything wrong. 80 | func ValidateLeaderMigrationConfiguration(config *internal.LeaderMigrationConfiguration) (allErrs field.ErrorList) { 81 | if config.LeaderName == "" { 82 | allErrs = append(allErrs, field.Required(field.NewPath("leaderName"), 83 | "leaderName must be set for LeaderMigrationConfiguration")) 84 | } 85 | if config.ResourceLock != ResourceLockLeases && config.ResourceLock != ResourceLockEndpoints { 86 | allErrs = append(allErrs, field.Invalid(field.NewPath("resourceLock"), config.ResourceLock, 87 | "resource Lock must be one of 'leases' or 'endpoints'")) 88 | } 89 | // validate controllerLeaders 90 | fldPath := field.NewPath("controllerLeaders") 91 | for i, controllerLeader := range config.ControllerLeaders { 92 | path := fldPath.Index(i) 93 | allErrs = append(allErrs, validateControllerLeaderConfiguration(path, &controllerLeader)...) 94 | } 95 | return 96 | } 97 | 98 | func validateControllerLeaderConfiguration(path *field.Path, config *internal.ControllerLeaderConfiguration) (allErrs field.ErrorList) { 99 | if config == nil { 100 | return 101 | } 102 | if config.Component == "" { 103 | allErrs = append(allErrs, field.Required(path.Child("component"), "")) 104 | } 105 | if config.Name == "" { 106 | allErrs = append(allErrs, field.Required(path.Child("name"), "")) 107 | } 108 | return 109 | } 110 | -------------------------------------------------------------------------------- /pkg/clientbuilder/client_builder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package clientbuilder 18 | 19 | import ( 20 | "k8s.io/client-go/discovery" 21 | clientset "k8s.io/client-go/kubernetes" 22 | restclient "k8s.io/client-go/rest" 23 | "k8s.io/klog/v2" 24 | ) 25 | 26 | // ControllerClientBuilder allows you to get clients and configs for controllers 27 | // Please note a copy also exists in staging/src/k8s.io/cloud-provider/cloud.go 28 | // TODO: Extract this into a separate controller utilities repo (issues/68947) 29 | type ControllerClientBuilder interface { 30 | Config(name string) (*restclient.Config, error) 31 | ConfigOrDie(name string) *restclient.Config 32 | Client(name string) (clientset.Interface, error) 33 | ClientOrDie(name string) clientset.Interface 34 | DiscoveryClient(name string) (discovery.DiscoveryInterface, error) 35 | DiscoveryClientOrDie(name string) discovery.DiscoveryInterface 36 | } 37 | 38 | // SimpleControllerClientBuilder returns a fixed client with different user agents 39 | type SimpleControllerClientBuilder struct { 40 | // ClientConfig is a skeleton config to clone and use as the basis for each controller client 41 | ClientConfig *restclient.Config 42 | } 43 | 44 | // Config returns a client config for a fixed client 45 | func (b SimpleControllerClientBuilder) Config(name string) (*restclient.Config, error) { 46 | clientConfig := *b.ClientConfig 47 | return restclient.AddUserAgent(&clientConfig, name), nil 48 | } 49 | 50 | // ConfigOrDie returns a client config if no error from previous config func. 51 | // If it gets an error getting the client, it will log the error and kill the process it's running in. 52 | func (b SimpleControllerClientBuilder) ConfigOrDie(name string) *restclient.Config { 53 | clientConfig, err := b.Config(name) 54 | if err != nil { 55 | klog.Fatal(err) 56 | } 57 | return clientConfig 58 | } 59 | 60 | // Client returns a clientset.Interface built from the ClientBuilder 61 | func (b SimpleControllerClientBuilder) Client(name string) (clientset.Interface, error) { 62 | clientConfig, err := b.Config(name) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return clientset.NewForConfig(clientConfig) 67 | } 68 | 69 | // ClientOrDie returns a clientset.interface built from the ClientBuilder with no error. 70 | // If it gets an error getting the client, it will log the error and kill the process it's running in. 71 | func (b SimpleControllerClientBuilder) ClientOrDie(name string) clientset.Interface { 72 | client, err := b.Client(name) 73 | if err != nil { 74 | klog.Fatal(err) 75 | } 76 | return client 77 | } 78 | 79 | // DiscoveryClient returns a discovery.DiscoveryInterface built from the ClientBuilder 80 | // Discovery is special because it will artificially pump the burst quite high to handle the many discovery requests. 81 | func (b SimpleControllerClientBuilder) DiscoveryClient(name string) (discovery.DiscoveryInterface, error) { 82 | clientConfig, err := b.Config(name) 83 | if err != nil { 84 | return nil, err 85 | } 86 | // Discovery makes a lot of requests infrequently. This allows the burst to succeed and refill to happen 87 | // in just a few seconds. 88 | clientConfig.Burst = 200 89 | clientConfig.QPS = 20 90 | return clientset.NewForConfig(clientConfig) 91 | } 92 | 93 | // DiscoveryClientOrDie returns a discovery.DiscoveryInterface built from the ClientBuilder with no error. 94 | // Discovery is special because it will artificially pump the burst quite high to handle the many discovery requests. 95 | // If it gets an error getting the client, it will log the error and kill the process it's running in. 96 | func (b SimpleControllerClientBuilder) DiscoveryClientOrDie(name string) discovery.DiscoveryInterface { 97 | client, err := b.DiscoveryClient(name) 98 | if err != nil { 99 | klog.Fatal(err) 100 | } 101 | return client 102 | } 103 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // This is a generated file. Do not edit directly. 2 | 3 | module k8s.io/controller-manager 4 | 5 | go 1.25.0 6 | 7 | godebug default=go1.25 8 | 9 | require ( 10 | github.com/spf13/pflag v1.0.9 11 | github.com/stretchr/testify v1.11.1 12 | golang.org/x/oauth2 v0.30.0 13 | k8s.io/api v0.0.0-20251204222646-382014e64b8e 14 | k8s.io/apimachinery v0.0.0-20251204222123-56aa7d5cc8bb 15 | k8s.io/apiserver v0.0.0-20251204230936-a52843043e97 16 | k8s.io/client-go v0.0.0-20251204223340-453ad29ccd47 17 | k8s.io/component-base v0.0.0-20251204225730-8cb15f10375f 18 | k8s.io/klog/v2 v2.130.1 19 | k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 20 | ) 21 | 22 | require ( 23 | cel.dev/expr v0.24.0 // indirect 24 | github.com/NYTimes/gziphandler v1.1.1 // indirect 25 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 26 | github.com/beorn7/perks v1.0.1 // indirect 27 | github.com/blang/semver/v4 v4.0.0 // indirect 28 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 29 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 30 | github.com/coreos/go-semver v0.3.1 // indirect 31 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 32 | github.com/davecgh/go-spew v1.1.1 // indirect 33 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 34 | github.com/felixge/httpsnoop v1.0.4 // indirect 35 | github.com/fsnotify/fsnotify v1.9.0 // indirect 36 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 37 | github.com/go-logr/logr v1.4.3 // indirect 38 | github.com/go-logr/stdr v1.2.2 // indirect 39 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 40 | github.com/go-openapi/jsonreference v0.20.2 // indirect 41 | github.com/go-openapi/swag v0.23.0 // indirect 42 | github.com/gogo/protobuf v1.3.2 // indirect 43 | github.com/golang/protobuf v1.5.4 // indirect 44 | github.com/google/cel-go v0.26.0 // indirect 45 | github.com/google/gnostic-models v0.7.0 // indirect 46 | github.com/google/go-cmp v0.7.0 // indirect 47 | github.com/google/uuid v1.6.0 // indirect 48 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 49 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect 50 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 51 | github.com/josharian/intern v1.0.0 // indirect 52 | github.com/json-iterator/go v1.1.12 // indirect 53 | github.com/kylelemons/godebug v1.1.0 // indirect 54 | github.com/mailru/easyjson v0.7.7 // indirect 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 56 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect 57 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 58 | github.com/pmezard/go-difflib v1.0.0 // indirect 59 | github.com/prometheus/client_golang v1.23.2 // indirect 60 | github.com/prometheus/client_model v0.6.2 // indirect 61 | github.com/prometheus/common v0.66.1 // indirect 62 | github.com/prometheus/procfs v0.16.1 // indirect 63 | github.com/spf13/cobra v1.10.0 // indirect 64 | github.com/stoewer/go-strcase v1.3.0 // indirect 65 | github.com/x448/float16 v0.8.4 // indirect 66 | go.etcd.io/etcd/api/v3 v3.6.5 // indirect 67 | go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect 68 | go.etcd.io/etcd/client/v3 v3.6.5 // indirect 69 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 70 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect 71 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect 72 | go.opentelemetry.io/otel v1.36.0 // indirect 73 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect 74 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect 75 | go.opentelemetry.io/otel/metric v1.36.0 // indirect 76 | go.opentelemetry.io/otel/sdk v1.36.0 // indirect 77 | go.opentelemetry.io/otel/trace v1.36.0 // indirect 78 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 79 | go.uber.org/multierr v1.11.0 // indirect 80 | go.uber.org/zap v1.27.0 // indirect 81 | go.yaml.in/yaml/v2 v2.4.3 // indirect 82 | go.yaml.in/yaml/v3 v3.0.4 // indirect 83 | golang.org/x/crypto v0.45.0 // indirect 84 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 85 | golang.org/x/net v0.47.0 // indirect 86 | golang.org/x/sync v0.18.0 // indirect 87 | golang.org/x/sys v0.38.0 // indirect 88 | golang.org/x/term v0.37.0 // indirect 89 | golang.org/x/text v0.31.0 // indirect 90 | golang.org/x/time v0.9.0 // indirect 91 | google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect 92 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect 93 | google.golang.org/grpc v1.72.2 // indirect 94 | google.golang.org/protobuf v1.36.8 // indirect 95 | gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect 96 | gopkg.in/inf.v0 v0.9.1 // indirect 97 | gopkg.in/yaml.v3 v3.0.1 // indirect 98 | k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect 99 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect 100 | sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect 101 | sigs.k8s.io/randfill v1.0.0 // indirect 102 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect 103 | sigs.k8s.io/yaml v1.6.0 // indirect 104 | ) 105 | -------------------------------------------------------------------------------- /pkg/leadermigration/migrator_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package leadermigration 18 | 19 | import ( 20 | "testing" 21 | 22 | internal "k8s.io/controller-manager/config" 23 | ) 24 | 25 | func TestLeaderMigratorFilterFunc(t *testing.T) { 26 | fromConfig := &internal.LeaderMigrationConfiguration{ 27 | ResourceLock: "leases", 28 | LeaderName: "cloud-provider-extraction-migration", 29 | ControllerLeaders: []internal.ControllerLeaderConfiguration{ 30 | { 31 | Name: "route-controller", 32 | Component: "kube-controller-manager", 33 | }, { 34 | Name: "service-controller", 35 | Component: "kube-controller-manager", 36 | }, { 37 | Name: "cloud-node-lifecycle-controller", 38 | Component: "kube-controller-manager", 39 | }, 40 | }, 41 | } 42 | toConfig := &internal.LeaderMigrationConfiguration{ 43 | ResourceLock: "leases", 44 | LeaderName: "cloud-provider-extraction-migration", 45 | ControllerLeaders: []internal.ControllerLeaderConfiguration{ 46 | { 47 | Name: "route-controller", 48 | Component: "cloud-controller-manager", 49 | }, { 50 | Name: "service-controller", 51 | Component: "cloud-controller-manager", 52 | }, { 53 | Name: "cloud-node-lifecycle-controller", 54 | Component: "cloud-controller-manager", 55 | }, 56 | }, 57 | } 58 | wildcardConfig := &internal.LeaderMigrationConfiguration{ 59 | ResourceLock: "leases", 60 | LeaderName: "cloud-provider-extraction-migration", 61 | ControllerLeaders: []internal.ControllerLeaderConfiguration{ 62 | { 63 | Name: "route-controller", 64 | Component: "*", 65 | }, { 66 | Name: "service-controller", 67 | Component: "*", 68 | }, { 69 | Name: "cloud-node-lifecycle-controller", 70 | Component: "*", 71 | }, 72 | }, 73 | } 74 | for _, tc := range []struct { 75 | name string 76 | config *internal.LeaderMigrationConfiguration 77 | component string 78 | migrated bool 79 | expectResult map[string]FilterResult 80 | }{ 81 | { 82 | name: "from config, kcm", 83 | config: fromConfig, 84 | component: "kube-controller-manager", 85 | expectResult: map[string]FilterResult{ 86 | "deployment-controller": ControllerNonMigrated, 87 | "route-controller": ControllerMigrated, 88 | "service-controller": ControllerMigrated, 89 | "cloud-node-lifecycle-controller": ControllerMigrated, 90 | }, 91 | }, 92 | { 93 | name: "from config, ccm", 94 | config: fromConfig, 95 | component: "cloud-controller-manager", 96 | expectResult: map[string]FilterResult{ 97 | "cloud-node": ControllerNonMigrated, 98 | "route-controller": ControllerUnowned, 99 | "service-controller": ControllerUnowned, 100 | "cloud-node-lifecycle-controller": ControllerUnowned, 101 | }, 102 | }, 103 | { 104 | name: "to config, kcm", 105 | config: toConfig, 106 | component: "kube-controller-manager", 107 | expectResult: map[string]FilterResult{ 108 | "deployment-controller": ControllerNonMigrated, 109 | "route-controller": ControllerUnowned, 110 | "service-controller": ControllerUnowned, 111 | "cloud-node-lifecycle-controller": ControllerUnowned, 112 | }, 113 | }, 114 | { 115 | name: "to config, ccm", 116 | config: toConfig, 117 | component: "cloud-controller-manager", 118 | expectResult: map[string]FilterResult{ 119 | "cloud-node-controller": ControllerNonMigrated, 120 | "route-controller": ControllerMigrated, 121 | "service-controller": ControllerMigrated, 122 | "cloud-node-lifecycle-controller": ControllerMigrated, 123 | }, 124 | }, 125 | { 126 | name: "wildcard config, kcm", 127 | config: wildcardConfig, 128 | component: "kube-controller-manager", 129 | expectResult: map[string]FilterResult{ 130 | "deployment-controller": ControllerNonMigrated, // KCM only 131 | "route-controller": ControllerMigrated, 132 | "service-controller": ControllerMigrated, 133 | "cloud-node-lifecycle-controller": ControllerMigrated, 134 | }, 135 | }, 136 | { 137 | name: "wildcard config, ccm", 138 | config: wildcardConfig, 139 | component: "cloud-controller-manager", 140 | expectResult: map[string]FilterResult{ 141 | "cloud-node-controller": ControllerNonMigrated, // CCM only 142 | "route-controller": ControllerMigrated, 143 | "service-controller": ControllerMigrated, 144 | "cloud-node-lifecycle-controller": ControllerMigrated, 145 | }, 146 | }, 147 | } { 148 | t.Run(tc.name, func(t *testing.T) { 149 | migrator := NewLeaderMigrator(tc.config, tc.component) 150 | for name, expected := range tc.expectResult { 151 | if result := migrator.FilterFunc(name); expected != result { 152 | t.Errorf("controller %s, expect %v, got %v", name, expected, result) 153 | } 154 | } 155 | }) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /pkg/healthz/handler_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package healthz 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | "net/http/httptest" 23 | "sync/atomic" 24 | "testing" 25 | "time" 26 | 27 | "k8s.io/apiserver/pkg/server/healthz" 28 | ) 29 | 30 | func TestMutableHealthzHandler(t *testing.T) { 31 | badChecker := healthz.NamedCheck("bad", func(r *http.Request) error { 32 | return fmt.Errorf("bad") 33 | }) 34 | for _, tc := range []struct { 35 | name string 36 | checkBatches [][]healthz.HealthChecker 37 | appendBad bool // appends bad check after batches above, and see if it fails afterwards 38 | path string 39 | expectedBody string 40 | expectedStatus int 41 | }{ 42 | { 43 | name: "empty", 44 | checkBatches: [][]healthz.HealthChecker{}, 45 | path: "/healthz", 46 | expectedBody: "ok", 47 | expectedStatus: http.StatusOK, 48 | }, 49 | { 50 | name: "good", 51 | checkBatches: [][]healthz.HealthChecker{ 52 | {NamedPingChecker("good")}, 53 | }, 54 | path: "/healthz", 55 | expectedBody: "ok", 56 | expectedStatus: http.StatusOK, 57 | }, 58 | { 59 | name: "good verbose", // verbose only applies for successful checks 60 | checkBatches: [][]healthz.HealthChecker{ 61 | {NamedPingChecker("good")}, // batch 1: good 62 | }, 63 | path: "/healthz?verbose=true", 64 | expectedBody: "[+]good ok\nhealthz check passed\n", 65 | expectedStatus: http.StatusOK, 66 | }, 67 | { 68 | name: "good and bad, same batch", 69 | checkBatches: [][]healthz.HealthChecker{ 70 | {NamedPingChecker("good"), badChecker}, // batch 1: good, bad 71 | }, 72 | path: "/healthz", 73 | expectedBody: "[+]good ok\n[-]bad failed: reason withheld\nhealthz check failed\n", 74 | expectedStatus: http.StatusInternalServerError, 75 | }, 76 | { 77 | name: "good and bad, two batches", 78 | checkBatches: [][]healthz.HealthChecker{ 79 | {NamedPingChecker("good")}, // batch 1: good 80 | {badChecker}, // batch 2: bad 81 | }, 82 | path: "/healthz", 83 | expectedBody: "[+]good ok\n[-]bad failed: reason withheld\nhealthz check failed\n", 84 | expectedStatus: http.StatusInternalServerError, 85 | }, 86 | { 87 | name: "two checks and append bad", 88 | checkBatches: [][]healthz.HealthChecker{ 89 | {NamedPingChecker("foo"), NamedPingChecker("bar")}, 90 | }, 91 | path: "/healthz", 92 | expectedBody: "ok", 93 | expectedStatus: http.StatusOK, 94 | appendBad: true, 95 | }, 96 | { 97 | name: "subcheck", 98 | checkBatches: [][]healthz.HealthChecker{ 99 | {NamedPingChecker("good")}, // batch 1: good 100 | {badChecker}, // batch 2: bad 101 | }, 102 | path: "/healthz/good", 103 | expectedBody: "ok", 104 | expectedStatus: http.StatusOK, 105 | }, 106 | } { 107 | t.Run(tc.name, func(t *testing.T) { 108 | h := NewMutableHealthzHandler() 109 | for _, batch := range tc.checkBatches { 110 | h.AddHealthChecker(batch...) 111 | } 112 | req, err := http.NewRequest("GET", fmt.Sprintf("https://example.com%v", tc.path), nil) 113 | if err != nil { 114 | t.Fatalf("unexpected error: %v", err) 115 | } 116 | w := httptest.NewRecorder() 117 | h.ServeHTTP(w, req) 118 | if w.Code != tc.expectedStatus { 119 | t.Errorf("unexpected status: expected %v, got %v", tc.expectedStatus, w.Result().StatusCode) 120 | } 121 | if w.Body.String() != tc.expectedBody { 122 | t.Errorf("unexpected body: expected %v, got %v", tc.expectedBody, w.Body.String()) 123 | } 124 | if tc.appendBad { 125 | h.AddHealthChecker(badChecker) 126 | w := httptest.NewRecorder() 127 | h.ServeHTTP(w, req) 128 | // should fail 129 | if w.Code != http.StatusInternalServerError { 130 | t.Errorf("did not fail after adding bad checker") 131 | } 132 | } 133 | }) 134 | } 135 | } 136 | 137 | // TestConcurrentChecks tests that the handler would not block on concurrent healthz requests. 138 | func TestConcurrentChecks(t *testing.T) { 139 | const N = 5 140 | stopChan := make(chan interface{}) 141 | defer close(stopChan) // always close no matter passing or not 142 | concurrentChan := make(chan interface{}, N) 143 | var concurrentCount int32 144 | pausingCheck := healthz.NamedCheck("pausing", func(r *http.Request) error { 145 | atomic.AddInt32(&concurrentCount, 1) 146 | concurrentChan <- nil 147 | <-stopChan 148 | return nil 149 | }) 150 | 151 | h := NewMutableHealthzHandler(pausingCheck) 152 | for i := 0; i < N; i++ { 153 | go func() { 154 | req, _ := http.NewRequest(http.MethodGet, "https://example.com/healthz", nil) 155 | w := httptest.NewRecorder() 156 | h.ServeHTTP(w, req) 157 | }() 158 | } 159 | 160 | giveUp := time.After(1 * time.Second) // should take <1ms if passing 161 | for i := 0; i < N; i++ { 162 | select { 163 | case <-giveUp: 164 | t.Errorf("given up waiting for concurrent checks to start.") 165 | return 166 | case <-concurrentChan: 167 | continue 168 | } 169 | } 170 | 171 | if concurrentCount != N { 172 | t.Errorf("expected %v concurrency, got %v", N, concurrentCount) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /config/v1beta1/zz_generated.conversion.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by conversion-gen. DO NOT EDIT. 21 | 22 | package v1beta1 23 | 24 | import ( 25 | unsafe "unsafe" 26 | 27 | conversion "k8s.io/apimachinery/pkg/conversion" 28 | runtime "k8s.io/apimachinery/pkg/runtime" 29 | config "k8s.io/controller-manager/config" 30 | ) 31 | 32 | func init() { 33 | localSchemeBuilder.Register(RegisterConversions) 34 | } 35 | 36 | // RegisterConversions adds conversion functions to the given scheme. 37 | // Public to allow building arbitrary schemes. 38 | func RegisterConversions(s *runtime.Scheme) error { 39 | if err := s.AddGeneratedConversionFunc((*ControllerLeaderConfiguration)(nil), (*config.ControllerLeaderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 40 | return Convert_v1beta1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(a.(*ControllerLeaderConfiguration), b.(*config.ControllerLeaderConfiguration), scope) 41 | }); err != nil { 42 | return err 43 | } 44 | if err := s.AddGeneratedConversionFunc((*config.ControllerLeaderConfiguration)(nil), (*ControllerLeaderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 45 | return Convert_config_ControllerLeaderConfiguration_To_v1beta1_ControllerLeaderConfiguration(a.(*config.ControllerLeaderConfiguration), b.(*ControllerLeaderConfiguration), scope) 46 | }); err != nil { 47 | return err 48 | } 49 | if err := s.AddGeneratedConversionFunc((*LeaderMigrationConfiguration)(nil), (*config.LeaderMigrationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 50 | return Convert_v1beta1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(a.(*LeaderMigrationConfiguration), b.(*config.LeaderMigrationConfiguration), scope) 51 | }); err != nil { 52 | return err 53 | } 54 | if err := s.AddGeneratedConversionFunc((*config.LeaderMigrationConfiguration)(nil), (*LeaderMigrationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 55 | return Convert_config_LeaderMigrationConfiguration_To_v1beta1_LeaderMigrationConfiguration(a.(*config.LeaderMigrationConfiguration), b.(*LeaderMigrationConfiguration), scope) 56 | }); err != nil { 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | func autoConvert_v1beta1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(in *ControllerLeaderConfiguration, out *config.ControllerLeaderConfiguration, s conversion.Scope) error { 63 | out.Name = in.Name 64 | out.Component = in.Component 65 | return nil 66 | } 67 | 68 | // Convert_v1beta1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration is an autogenerated conversion function. 69 | func Convert_v1beta1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(in *ControllerLeaderConfiguration, out *config.ControllerLeaderConfiguration, s conversion.Scope) error { 70 | return autoConvert_v1beta1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(in, out, s) 71 | } 72 | 73 | func autoConvert_config_ControllerLeaderConfiguration_To_v1beta1_ControllerLeaderConfiguration(in *config.ControllerLeaderConfiguration, out *ControllerLeaderConfiguration, s conversion.Scope) error { 74 | out.Name = in.Name 75 | out.Component = in.Component 76 | return nil 77 | } 78 | 79 | // Convert_config_ControllerLeaderConfiguration_To_v1beta1_ControllerLeaderConfiguration is an autogenerated conversion function. 80 | func Convert_config_ControllerLeaderConfiguration_To_v1beta1_ControllerLeaderConfiguration(in *config.ControllerLeaderConfiguration, out *ControllerLeaderConfiguration, s conversion.Scope) error { 81 | return autoConvert_config_ControllerLeaderConfiguration_To_v1beta1_ControllerLeaderConfiguration(in, out, s) 82 | } 83 | 84 | func autoConvert_v1beta1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(in *LeaderMigrationConfiguration, out *config.LeaderMigrationConfiguration, s conversion.Scope) error { 85 | out.LeaderName = in.LeaderName 86 | out.ResourceLock = in.ResourceLock 87 | out.ControllerLeaders = *(*[]config.ControllerLeaderConfiguration)(unsafe.Pointer(&in.ControllerLeaders)) 88 | return nil 89 | } 90 | 91 | // Convert_v1beta1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration is an autogenerated conversion function. 92 | func Convert_v1beta1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(in *LeaderMigrationConfiguration, out *config.LeaderMigrationConfiguration, s conversion.Scope) error { 93 | return autoConvert_v1beta1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(in, out, s) 94 | } 95 | 96 | func autoConvert_config_LeaderMigrationConfiguration_To_v1beta1_LeaderMigrationConfiguration(in *config.LeaderMigrationConfiguration, out *LeaderMigrationConfiguration, s conversion.Scope) error { 97 | out.LeaderName = in.LeaderName 98 | out.ResourceLock = in.ResourceLock 99 | out.ControllerLeaders = *(*[]ControllerLeaderConfiguration)(unsafe.Pointer(&in.ControllerLeaders)) 100 | return nil 101 | } 102 | 103 | // Convert_config_LeaderMigrationConfiguration_To_v1beta1_LeaderMigrationConfiguration is an autogenerated conversion function. 104 | func Convert_config_LeaderMigrationConfiguration_To_v1beta1_LeaderMigrationConfiguration(in *config.LeaderMigrationConfiguration, out *LeaderMigrationConfiguration, s conversion.Scope) error { 105 | return autoConvert_config_LeaderMigrationConfiguration_To_v1beta1_LeaderMigrationConfiguration(in, out, s) 106 | } 107 | -------------------------------------------------------------------------------- /options/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "k8s.io/apimachinery/pkg/util/sets" 24 | cliflag "k8s.io/component-base/cli/flag" 25 | "k8s.io/component-base/config/options" 26 | cmconfig "k8s.io/controller-manager/config" 27 | migration "k8s.io/controller-manager/pkg/leadermigration/options" 28 | ) 29 | 30 | // GenericControllerManagerConfigurationOptions holds the options which are generic. 31 | type GenericControllerManagerConfigurationOptions struct { 32 | *cmconfig.GenericControllerManagerConfiguration 33 | Debugging *DebuggingOptions 34 | // LeaderMigration is the options for leader migration, a nil indicates default options should be applied. 35 | LeaderMigration *migration.LeaderMigrationOptions 36 | } 37 | 38 | // NewGenericControllerManagerConfigurationOptions returns generic configuration default values for both 39 | // the kube-controller-manager and the cloud-contoller-manager. Any common changes should 40 | // be made here. Any individual changes should be made in that controller. 41 | func NewGenericControllerManagerConfigurationOptions(cfg *cmconfig.GenericControllerManagerConfiguration) *GenericControllerManagerConfigurationOptions { 42 | o := &GenericControllerManagerConfigurationOptions{ 43 | GenericControllerManagerConfiguration: cfg, 44 | Debugging: RecommendedDebuggingOptions(), 45 | LeaderMigration: &migration.LeaderMigrationOptions{}, 46 | } 47 | 48 | return o 49 | } 50 | 51 | // AddFlags adds flags related to generic for controller manager to the specified FlagSet. 52 | func (o *GenericControllerManagerConfigurationOptions) AddFlags(fss *cliflag.NamedFlagSets, allControllers, disabledByDefaultControllers []string, controllerAliases map[string]string) { 53 | if o == nil { 54 | return 55 | } 56 | 57 | o.Debugging.AddFlags(fss.FlagSet("debugging")) 58 | o.LeaderMigration.AddFlags(fss.FlagSet("leader-migration")) 59 | genericfs := fss.FlagSet("generic") 60 | genericfs.DurationVar(&o.MinResyncPeriod.Duration, "min-resync-period", o.MinResyncPeriod.Duration, "The resync period in reflectors will be random between MinResyncPeriod and 2*MinResyncPeriod.") 61 | genericfs.StringVar(&o.ClientConnection.ContentType, "kube-api-content-type", o.ClientConnection.ContentType, "Content type of requests sent to apiserver.") 62 | genericfs.Float32Var(&o.ClientConnection.QPS, "kube-api-qps", o.ClientConnection.QPS, "QPS to use while talking with kubernetes apiserver.") 63 | genericfs.Int32Var(&o.ClientConnection.Burst, "kube-api-burst", o.ClientConnection.Burst, "Burst to use while talking with kubernetes apiserver.") 64 | genericfs.DurationVar(&o.ControllerStartInterval.Duration, "controller-start-interval", o.ControllerStartInterval.Duration, "Interval between starting controller managers.") 65 | genericfs.StringSliceVar(&o.Controllers, "controllers", o.Controllers, fmt.Sprintf(""+ 66 | "A list of controllers to enable. '*' enables all on-by-default controllers, 'foo' enables the controller "+ 67 | "named 'foo', '-foo' disables the controller named 'foo'.\nAll controllers: %s\nDisabled-by-default controllers: %s", 68 | strings.Join(allControllers, ", "), strings.Join(disabledByDefaultControllers, ", "))) 69 | 70 | options.BindLeaderElectionFlags(&o.LeaderElection, genericfs) 71 | } 72 | 73 | // ApplyTo fills up generic config with options. 74 | func (o *GenericControllerManagerConfigurationOptions) ApplyTo(cfg *cmconfig.GenericControllerManagerConfiguration, allControllers []string, disabledByDefaultControllers []string, controllerAliases map[string]string) error { 75 | if o == nil { 76 | return nil 77 | } 78 | 79 | if err := o.Debugging.ApplyTo(&cfg.Debugging); err != nil { 80 | return err 81 | } 82 | if err := o.LeaderMigration.ApplyTo(cfg); err != nil { 83 | return err 84 | } 85 | cfg.Port = o.Port 86 | cfg.Address = o.Address 87 | cfg.MinResyncPeriod = o.MinResyncPeriod 88 | cfg.ClientConnection = o.ClientConnection 89 | cfg.ControllerStartInterval = o.ControllerStartInterval 90 | cfg.LeaderElection = o.LeaderElection 91 | 92 | // copy controller names and replace aliases with canonical names 93 | cfg.Controllers = make([]string, len(o.Controllers)) 94 | for i, initialName := range o.Controllers { 95 | initialNameWithoutPrefix := strings.TrimPrefix(initialName, "-") 96 | controllerName := initialNameWithoutPrefix 97 | if canonicalName, ok := controllerAliases[controllerName]; ok { 98 | controllerName = canonicalName 99 | } 100 | if strings.HasPrefix(initialName, "-") { 101 | controllerName = fmt.Sprintf("-%s", controllerName) 102 | } 103 | cfg.Controllers[i] = controllerName 104 | } 105 | 106 | return nil 107 | } 108 | 109 | // Validate checks validation of GenericOptions. 110 | func (o *GenericControllerManagerConfigurationOptions) Validate(allControllers []string, disabledByDefaultControllers []string, controllerAliases map[string]string) []error { 111 | if o == nil { 112 | return nil 113 | } 114 | 115 | errs := []error{} 116 | errs = append(errs, o.Debugging.Validate()...) 117 | 118 | // TODO: This can be removed when ResourceLock is not available 119 | // Lock the ResourceLock using leases 120 | if o.LeaderElection.LeaderElect && o.LeaderElection.ResourceLock != "leases" { 121 | errs = append(errs, fmt.Errorf(`resourceLock value must be "leases"`)) 122 | } 123 | 124 | allControllersSet := sets.NewString(allControllers...) 125 | for _, initialName := range o.Controllers { 126 | if initialName == "*" { 127 | continue 128 | } 129 | initialNameWithoutPrefix := strings.TrimPrefix(initialName, "-") 130 | controllerName := initialNameWithoutPrefix 131 | if canonicalName, ok := controllerAliases[controllerName]; ok { 132 | controllerName = canonicalName 133 | } 134 | if !allControllersSet.Has(controllerName) { 135 | errs = append(errs, fmt.Errorf("%q is not in the list of known controllers", initialNameWithoutPrefix)) 136 | } 137 | } 138 | 139 | return errs 140 | } 141 | -------------------------------------------------------------------------------- /pkg/leadermigration/config/config_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "os" 21 | "reflect" 22 | "testing" 23 | 24 | utiltesting "k8s.io/client-go/util/testing" 25 | 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | internal "k8s.io/controller-manager/config" 28 | ) 29 | 30 | func TestReadLeaderMigrationConfiguration(t *testing.T) { 31 | testCases := []struct { 32 | name string 33 | content string 34 | expected *internal.LeaderMigrationConfiguration 35 | expectErr bool 36 | }{ 37 | { 38 | name: "empty", 39 | content: "", 40 | expected: nil, 41 | expectErr: true, 42 | }, 43 | { 44 | name: "wrong type", 45 | content: ` 46 | apiVersion: kubelet.config.k8s.io/v1beta1 47 | kind: KubeletConfiguration 48 | `, 49 | expected: nil, 50 | expectErr: true, 51 | }, 52 | { 53 | name: "basic", 54 | content: ` 55 | apiVersion: controllermanager.config.k8s.io/v1alpha1 56 | kind: LeaderMigrationConfiguration 57 | leaderName: migration-120-to-121 58 | resourceLock: leases 59 | controllerLeaders: [] 60 | `, 61 | expected: &internal.LeaderMigrationConfiguration{ 62 | TypeMeta: metav1.TypeMeta{}, 63 | LeaderName: "migration-120-to-121", 64 | ResourceLock: "leases", 65 | ControllerLeaders: []internal.ControllerLeaderConfiguration{}, 66 | }, 67 | expectErr: false, 68 | }, 69 | { 70 | name: "endpoints", 71 | content: ` 72 | apiVersion: controllermanager.config.k8s.io/v1alpha1 73 | kind: LeaderMigrationConfiguration 74 | leaderName: migration-120-to-121 75 | resourceLock: endpoints 76 | controllerLeaders: [] 77 | `, 78 | expected: &internal.LeaderMigrationConfiguration{ 79 | TypeMeta: metav1.TypeMeta{}, 80 | LeaderName: "migration-120-to-121", 81 | ResourceLock: "endpoints", 82 | ControllerLeaders: []internal.ControllerLeaderConfiguration{}, 83 | }, 84 | }, 85 | { 86 | name: "unknown field causes error with strict validation", 87 | content: ` 88 | apiVersion: controllermanager.config.k8s.io/v1alpha1 89 | kind: LeaderMigrationConfiguration 90 | leaderName: migration-120-to-121 91 | resourceLock: endpoints 92 | foo: bar 93 | controllerLeaders: [] 94 | `, 95 | expectErr: true, 96 | }, 97 | { 98 | name: "duplicate field causes error with strict validation", 99 | content: ` 100 | apiVersion: controllermanager.config.k8s.io/v1alpha1 101 | kind: LeaderMigrationConfiguration 102 | leaderName: migration-120-to-121 103 | resourceLock: endpoints 104 | resourceLock: endpoints1 105 | controllerLeaders: [] 106 | `, 107 | expectErr: true, 108 | }, 109 | { 110 | name: "withLeaders", 111 | content: ` 112 | apiVersion: controllermanager.config.k8s.io/v1alpha1 113 | kind: LeaderMigrationConfiguration 114 | leaderName: migration-120-to-121 115 | resourceLock: endpoints 116 | controllerLeaders: 117 | - name: route-controller 118 | component: kube-controller-manager 119 | - name: service-controller 120 | component: kube-controller-manager 121 | `, 122 | expected: &internal.LeaderMigrationConfiguration{ 123 | TypeMeta: metav1.TypeMeta{}, 124 | LeaderName: "migration-120-to-121", 125 | ResourceLock: "endpoints", 126 | ControllerLeaders: []internal.ControllerLeaderConfiguration{ 127 | { 128 | Name: "route-controller", 129 | Component: "kube-controller-manager", 130 | }, 131 | { 132 | Name: "service-controller", 133 | Component: "kube-controller-manager", 134 | }, 135 | }, 136 | }, 137 | }, 138 | } 139 | 140 | for _, tc := range testCases { 141 | t.Run(tc.name, func(t *testing.T) { 142 | configFile, err := os.CreateTemp("", tc.name) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | defer utiltesting.CloseAndRemove(t, configFile) 147 | err = os.WriteFile(configFile.Name(), []byte(tc.content), os.FileMode(0755)) 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | result, err := ReadLeaderMigrationConfiguration(configFile.Name()) 152 | if tc.expectErr && err == nil { 153 | t.Errorf("unexpected no error for %s", tc.name) 154 | } else if !tc.expectErr && err != nil { 155 | t.Errorf("get error from ReadLeaderElectionConfiguration: %#v", err) 156 | } else if !reflect.DeepEqual(result, tc.expected) { 157 | t.Errorf("result not matching expected, got %#v, expected %#v", result, tc.expected) 158 | } 159 | }) 160 | } 161 | } 162 | 163 | func TestValidateLeaderMigrationConfiguration(t *testing.T) { 164 | testCases := []struct { 165 | name string 166 | config *internal.LeaderMigrationConfiguration 167 | expectErr bool 168 | }{ 169 | { 170 | name: "empty name", 171 | config: &internal.LeaderMigrationConfiguration{ 172 | LeaderName: "", 173 | ResourceLock: ResourceLockLeases, 174 | ControllerLeaders: []internal.ControllerLeaderConfiguration{}, 175 | }, 176 | expectErr: true, 177 | }, 178 | { 179 | name: "invalid resourceLock", 180 | config: &internal.LeaderMigrationConfiguration{ 181 | LeaderName: "test", 182 | ResourceLock: "invalid", 183 | ControllerLeaders: []internal.ControllerLeaderConfiguration{}, 184 | }, 185 | expectErr: true, 186 | }, 187 | { 188 | name: "empty controllerLeaders (valid)", 189 | config: &internal.LeaderMigrationConfiguration{ 190 | LeaderName: "test", 191 | ResourceLock: ResourceLockLeases, 192 | ControllerLeaders: []internal.ControllerLeaderConfiguration{}, 193 | }, 194 | expectErr: false, 195 | }, 196 | { 197 | name: "endpoints", 198 | config: &internal.LeaderMigrationConfiguration{ 199 | TypeMeta: metav1.TypeMeta{}, 200 | LeaderName: "migration-120-to-121", 201 | ResourceLock: ResourceLockEndpoints, 202 | ControllerLeaders: []internal.ControllerLeaderConfiguration{ 203 | { 204 | Name: "route-controller", 205 | Component: "kube-controller-manager", 206 | }, 207 | }, 208 | }, 209 | expectErr: false, 210 | }, 211 | } 212 | for _, tc := range testCases { 213 | t.Run(tc.name, func(t *testing.T) { 214 | errs := ValidateLeaderMigrationConfiguration(tc.config) 215 | if tc.expectErr && len(errs) == 0 { 216 | t.Errorf("calling ValidateLeaderMigrationConfiguration expected errors but got no error") 217 | } 218 | if !tc.expectErr && len(errs) != 0 { 219 | t.Errorf("calling ValidateLeaderMigrationConfiguration expected no error but got %v", errs) 220 | } 221 | }) 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /pkg/leadermigration/options/options_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "os" 21 | "reflect" 22 | "testing" 23 | 24 | utiltesting "k8s.io/client-go/util/testing" 25 | 26 | "github.com/spf13/pflag" 27 | 28 | "k8s.io/controller-manager/config" 29 | migrationconfig "k8s.io/controller-manager/pkg/leadermigration/config" 30 | ) 31 | 32 | func TestLeaderMigrationOptions(t *testing.T) { 33 | testCases := []struct { 34 | name string 35 | flags []string 36 | configContent string 37 | expectEnabled bool 38 | expectErr bool 39 | expectConfig *config.LeaderMigrationConfiguration 40 | }{ 41 | { 42 | name: "enabled, with default configuration", 43 | flags: []string{"--enable-leader-migration"}, 44 | expectEnabled: true, 45 | expectErr: false, 46 | expectConfig: migrationconfig.DefaultLeaderMigrationConfiguration(), 47 | }, 48 | { 49 | name: "enabled, with custom configuration file", 50 | flags: []string{"--enable-leader-migration"}, 51 | expectEnabled: true, 52 | configContent: ` 53 | apiVersion: controllermanager.config.k8s.io/v1alpha1 54 | kind: LeaderMigrationConfiguration 55 | leaderName: test-leader-migration 56 | resourceLock: leases 57 | controllerLeaders: [] 58 | `, 59 | expectErr: false, 60 | expectConfig: &config.LeaderMigrationConfiguration{ 61 | LeaderName: "test-leader-migration", 62 | ResourceLock: "leases", 63 | ControllerLeaders: []config.ControllerLeaderConfiguration{}, 64 | }, 65 | }, 66 | { 67 | name: "enabled, with custom configuration file (version v1beta1)", 68 | flags: []string{"--enable-leader-migration"}, 69 | expectEnabled: true, 70 | configContent: ` 71 | apiVersion: controllermanager.config.k8s.io/v1beta1 72 | kind: LeaderMigrationConfiguration 73 | leaderName: test-leader-migration 74 | resourceLock: leases 75 | controllerLeaders: [] 76 | `, 77 | expectErr: false, 78 | expectConfig: &config.LeaderMigrationConfiguration{ 79 | LeaderName: "test-leader-migration", 80 | ResourceLock: "leases", 81 | ControllerLeaders: []config.ControllerLeaderConfiguration{}, 82 | }, 83 | }, 84 | { 85 | name: "enabled, with custom configuration file (version v1)", 86 | flags: []string{"--enable-leader-migration"}, 87 | expectEnabled: true, 88 | configContent: ` 89 | apiVersion: controllermanager.config.k8s.io/v1 90 | kind: LeaderMigrationConfiguration 91 | leaderName: test-leader-migration 92 | controllerLeaders: [] 93 | `, 94 | expectErr: false, 95 | expectConfig: &config.LeaderMigrationConfiguration{ 96 | LeaderName: "test-leader-migration", 97 | ResourceLock: "leases", 98 | ControllerLeaders: []config.ControllerLeaderConfiguration{}, 99 | }, 100 | }, 101 | { 102 | name: "enabled, with populated controllerLeaders (version v1)", 103 | flags: []string{"--enable-leader-migration"}, 104 | expectEnabled: true, 105 | configContent: ` 106 | apiVersion: controllermanager.config.k8s.io/v1 107 | kind: LeaderMigrationConfiguration 108 | leaderName: test-leader-migration 109 | controllerLeaders: 110 | - name: route-controller 111 | component: "*" 112 | - name: service-controller 113 | component: "*" 114 | - name: cloud-node-lifecycle-controller 115 | component: "*" 116 | - name: node-ipam-controller 117 | component: "*" 118 | `, 119 | expectErr: false, 120 | expectConfig: &config.LeaderMigrationConfiguration{ 121 | LeaderName: "test-leader-migration", 122 | ResourceLock: "leases", 123 | ControllerLeaders: []config.ControllerLeaderConfiguration{ 124 | { 125 | Name: "route-controller", 126 | Component: "*", 127 | }, 128 | { 129 | Name: "service-controller", 130 | Component: "*", 131 | }, 132 | { 133 | Name: "cloud-node-lifecycle-controller", 134 | Component: "*", 135 | }, 136 | { 137 | Name: "node-ipam-controller", 138 | Component: "*", 139 | }, 140 | }, 141 | }, 142 | }, { 143 | name: "enabled, with non-wildcard controllerLeaders (version v1)", 144 | flags: []string{"--enable-leader-migration"}, 145 | expectEnabled: true, 146 | configContent: ` 147 | apiVersion: controllermanager.config.k8s.io/v1 148 | kind: LeaderMigrationConfiguration 149 | leaderName: test-leader-migration 150 | controllerLeaders: 151 | - name: route-controller 152 | component: "cloud-controller-manager" 153 | - name: service-controller 154 | component: "cloud-controller-manager" 155 | - name: cloud-node-lifecycle-controller 156 | component: "cloud-controller-manager" 157 | - name: node-ipam-controller 158 | component: "kube-controller-manager" 159 | `, 160 | expectErr: false, 161 | expectConfig: &config.LeaderMigrationConfiguration{ 162 | LeaderName: "test-leader-migration", 163 | ResourceLock: "leases", 164 | ControllerLeaders: []config.ControllerLeaderConfiguration{ 165 | { 166 | Name: "route-controller", 167 | Component: "cloud-controller-manager", 168 | }, 169 | { 170 | Name: "service-controller", 171 | Component: "cloud-controller-manager", 172 | }, 173 | { 174 | Name: "cloud-node-lifecycle-controller", 175 | Component: "cloud-controller-manager", 176 | }, 177 | { 178 | Name: "node-ipam-controller", 179 | Component: "kube-controller-manager", 180 | }, 181 | }, 182 | }, 183 | }, 184 | } 185 | for _, tc := range testCases { 186 | t.Run(tc.name, func(t *testing.T) { 187 | flags := tc.flags 188 | if tc.configContent != "" { 189 | configFile, err := os.CreateTemp("", tc.name) 190 | if err != nil { 191 | t.Fatal(err) 192 | } 193 | defer utiltesting.CloseAndRemove(t, configFile) 194 | err = os.WriteFile(configFile.Name(), []byte(tc.configContent), os.FileMode(0755)) 195 | if err != nil { 196 | t.Fatal(err) 197 | } 198 | flags = append(flags, "--leader-migration-config="+configFile.Name()) 199 | } 200 | genericConfig := new(config.GenericControllerManagerConfiguration) 201 | options := new(LeaderMigrationOptions) 202 | fs := pflag.NewFlagSet("addflagstest", pflag.ContinueOnError) 203 | options.AddFlags(fs) 204 | err := fs.Parse(flags) 205 | if err != nil { 206 | t.Errorf("cannot parse leader-migration-config: %v", err) 207 | return 208 | } 209 | err = options.ApplyTo(genericConfig) 210 | if err != nil { 211 | if !tc.expectErr { 212 | t.Errorf("unexpected error: %v", err) 213 | return 214 | } 215 | // expect err and got err, finish the test case. 216 | return 217 | } 218 | if err == nil && tc.expectErr { 219 | t.Errorf("expected error but got nil") 220 | return 221 | } 222 | if genericConfig.LeaderMigrationEnabled != tc.expectEnabled { 223 | t.Errorf("expected Enabled=%v, got %v", tc.expectEnabled, options.Enabled) 224 | return 225 | } 226 | if tc.expectEnabled && !reflect.DeepEqual(tc.expectConfig, &genericConfig.LeaderMigration) { 227 | t.Errorf("expected config %#v but got %#v", tc.expectConfig, genericConfig.LeaderMigration) 228 | } 229 | }) 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /options/generic_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "reflect" 21 | "strings" 22 | "testing" 23 | 24 | utilerrors "k8s.io/apimachinery/pkg/util/errors" 25 | "k8s.io/component-base/config" 26 | cmconfig "k8s.io/controller-manager/config" 27 | ) 28 | 29 | func TestValidateGenericControllerManagerConfigurationOptions(t *testing.T) { 30 | testCases := []struct { 31 | name string 32 | allControllers []string 33 | controllerAliases map[string]string 34 | options *GenericControllerManagerConfigurationOptions 35 | expectErrors bool 36 | expectedErrorSubString string 37 | }{ 38 | { 39 | name: "no controllers defined", 40 | allControllers: nil, 41 | controllerAliases: nil, 42 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 43 | Controllers: []string{ 44 | "*", 45 | }, 46 | }), 47 | }, 48 | { 49 | name: "recognizes empty controllers", 50 | allControllers: getAllControllers(), 51 | controllerAliases: getControllerAliases(), 52 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{}), 53 | }, 54 | { 55 | name: "recognizes controllers without any aliases", 56 | allControllers: getAllControllers(), 57 | controllerAliases: nil, 58 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 59 | Controllers: []string{ 60 | "blue-controller", 61 | }, 62 | }), 63 | }, 64 | { 65 | name: "recognizes valid controllers", 66 | allControllers: getAllControllers(), 67 | controllerAliases: getControllerAliases(), 68 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 69 | Controllers: []string{ 70 | "*", 71 | "-red-controller", 72 | "blue-controller", 73 | }, 74 | }), 75 | }, 76 | { 77 | name: "recognizes disabled controller", 78 | allControllers: getAllControllers(), 79 | controllerAliases: getControllerAliases(), 80 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 81 | Controllers: []string{ 82 | "green-controller", 83 | }, 84 | }), 85 | }, 86 | { 87 | name: "recognized aliased controller", 88 | allControllers: getAllControllers(), 89 | controllerAliases: getControllerAliases(), 90 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 91 | Controllers: []string{ 92 | "ultramarine-controller", 93 | "-pink-controller", 94 | }, 95 | }), 96 | }, 97 | { 98 | name: "does not recognize controller", 99 | allControllers: nil, 100 | controllerAliases: nil, 101 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 102 | Controllers: []string{ 103 | "red-controller", 104 | }, 105 | }), 106 | expectErrors: true, 107 | expectedErrorSubString: "\"red-controller\" is not in the list of known controllers", 108 | }, 109 | { 110 | name: "does not recognize controller with aliases", 111 | allControllers: getAllControllers(), 112 | controllerAliases: getControllerAliases(), 113 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 114 | Controllers: []string{ 115 | "crimson-controller", 116 | "grey-controller", 117 | }, 118 | }), 119 | expectErrors: true, 120 | expectedErrorSubString: "\"grey-controller\" is not in the list of known controllers", 121 | }, 122 | { 123 | name: "leader election accepts only leases", 124 | allControllers: getAllControllers(), 125 | controllerAliases: getControllerAliases(), 126 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 127 | LeaderElection: config.LeaderElectionConfiguration{ 128 | LeaderElect: true, 129 | ResourceLock: "configmapsleases", 130 | }, 131 | }), 132 | expectErrors: true, 133 | expectedErrorSubString: "resourceLock value must be \"leases\"", 134 | }, 135 | } 136 | 137 | for _, tc := range testCases { 138 | t.Run(tc.name, func(t *testing.T) { 139 | errs := tc.options.Validate(tc.allControllers, []string{"green-controller"}, tc.controllerAliases) 140 | if len(errs) > 0 && !tc.expectErrors { 141 | t.Errorf("expected no errors, errors found %+v", errs) 142 | } 143 | 144 | if len(errs) == 0 && tc.expectErrors { 145 | t.Errorf("expected errors, no errors found") 146 | } 147 | 148 | if len(errs) > 0 && tc.expectErrors { 149 | gotErr := utilerrors.NewAggregate(errs).Error() 150 | if !strings.Contains(gotErr, tc.expectedErrorSubString) { 151 | t.Errorf("expected error: %s, got err: %v", tc.expectedErrorSubString, gotErr) 152 | } 153 | } 154 | }) 155 | } 156 | } 157 | 158 | func TestApplyToGenericControllerManagerConfigurationOptions(t *testing.T) { 159 | testCases := []struct { 160 | name string 161 | allControllers []string 162 | controllerAliases map[string]string 163 | options *GenericControllerManagerConfigurationOptions 164 | expectedControllers []string 165 | }{ 166 | { 167 | name: "no controllers defined", 168 | allControllers: nil, 169 | controllerAliases: nil, 170 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 171 | Controllers: []string{ 172 | "*", 173 | }, 174 | }), 175 | expectedControllers: []string{ 176 | "*", 177 | }, 178 | }, 179 | { 180 | name: "empty aliases", 181 | allControllers: getAllControllers(), 182 | controllerAliases: nil, 183 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 184 | Controllers: []string{ 185 | "-blue-controller", 186 | }, 187 | }), 188 | expectedControllers: []string{ 189 | "-blue-controller", 190 | }, 191 | }, 192 | { 193 | name: "applies valid controllers", 194 | allControllers: getAllControllers(), 195 | controllerAliases: getControllerAliases(), 196 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 197 | Controllers: []string{ 198 | "*", 199 | "green-controller", 200 | "-red-controller", 201 | "blue-controller", 202 | }, 203 | }), 204 | expectedControllers: []string{ 205 | "*", 206 | "green-controller", 207 | "-red-controller", 208 | "blue-controller", 209 | }, 210 | }, 211 | { 212 | name: "resolves aliases", 213 | allControllers: getAllControllers(), 214 | controllerAliases: getControllerAliases(), 215 | options: NewGenericControllerManagerConfigurationOptions(&cmconfig.GenericControllerManagerConfiguration{ 216 | Controllers: []string{ 217 | "green-controller", 218 | "-crimson-controller", 219 | "ultramarine-controller", 220 | "-pink-controller", 221 | }, 222 | }), 223 | expectedControllers: []string{ 224 | "green-controller", 225 | "-red-controller", 226 | "blue-controller", 227 | "-red-controller", 228 | }, 229 | }, 230 | } 231 | 232 | for _, tc := range testCases { 233 | t.Run(tc.name, func(t *testing.T) { 234 | cfg := &cmconfig.GenericControllerManagerConfiguration{} 235 | err := tc.options.ApplyTo(cfg, tc.allControllers, []string{"green-controller"}, tc.controllerAliases) 236 | if err != nil { 237 | t.Errorf("expected no errors, error found: %v", err) 238 | } 239 | if !reflect.DeepEqual(cfg.Controllers, tc.expectedControllers) { 240 | t.Errorf("applyTo failed, expected controllers %q, got controllers %q", strings.Join(cfg.Controllers, ","), strings.Join(tc.expectedControllers, ",")) 241 | } 242 | }) 243 | } 244 | } 245 | 246 | func getAllControllers() []string { 247 | return []string{ 248 | "red-controller", 249 | "green-controller", 250 | "blue-controller", 251 | } 252 | } 253 | 254 | func getControllerAliases() map[string]string { 255 | return map[string]string{ 256 | "crimson-controller": "red-controller", 257 | "pink-controller": "red-controller", 258 | "ultramarine-controller": "blue-controller", 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /config/v1alpha1/zz_generated.conversion.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright The Kubernetes Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by conversion-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | unsafe "unsafe" 26 | 27 | conversion "k8s.io/apimachinery/pkg/conversion" 28 | runtime "k8s.io/apimachinery/pkg/runtime" 29 | configv1alpha1 "k8s.io/component-base/config/v1alpha1" 30 | config "k8s.io/controller-manager/config" 31 | ) 32 | 33 | func init() { 34 | localSchemeBuilder.Register(RegisterConversions) 35 | } 36 | 37 | // RegisterConversions adds conversion functions to the given scheme. 38 | // Public to allow building arbitrary schemes. 39 | func RegisterConversions(s *runtime.Scheme) error { 40 | if err := s.AddGeneratedConversionFunc((*ControllerLeaderConfiguration)(nil), (*config.ControllerLeaderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 41 | return Convert_v1alpha1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(a.(*ControllerLeaderConfiguration), b.(*config.ControllerLeaderConfiguration), scope) 42 | }); err != nil { 43 | return err 44 | } 45 | if err := s.AddGeneratedConversionFunc((*config.ControllerLeaderConfiguration)(nil), (*ControllerLeaderConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 46 | return Convert_config_ControllerLeaderConfiguration_To_v1alpha1_ControllerLeaderConfiguration(a.(*config.ControllerLeaderConfiguration), b.(*ControllerLeaderConfiguration), scope) 47 | }); err != nil { 48 | return err 49 | } 50 | if err := s.AddGeneratedConversionFunc((*LeaderMigrationConfiguration)(nil), (*config.LeaderMigrationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 51 | return Convert_v1alpha1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(a.(*LeaderMigrationConfiguration), b.(*config.LeaderMigrationConfiguration), scope) 52 | }); err != nil { 53 | return err 54 | } 55 | if err := s.AddGeneratedConversionFunc((*config.LeaderMigrationConfiguration)(nil), (*LeaderMigrationConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 56 | return Convert_config_LeaderMigrationConfiguration_To_v1alpha1_LeaderMigrationConfiguration(a.(*config.LeaderMigrationConfiguration), b.(*LeaderMigrationConfiguration), scope) 57 | }); err != nil { 58 | return err 59 | } 60 | if err := s.AddConversionFunc((*config.GenericControllerManagerConfiguration)(nil), (*GenericControllerManagerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 61 | return Convert_config_GenericControllerManagerConfiguration_To_v1alpha1_GenericControllerManagerConfiguration(a.(*config.GenericControllerManagerConfiguration), b.(*GenericControllerManagerConfiguration), scope) 62 | }); err != nil { 63 | return err 64 | } 65 | if err := s.AddConversionFunc((*GenericControllerManagerConfiguration)(nil), (*config.GenericControllerManagerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { 66 | return Convert_v1alpha1_GenericControllerManagerConfiguration_To_config_GenericControllerManagerConfiguration(a.(*GenericControllerManagerConfiguration), b.(*config.GenericControllerManagerConfiguration), scope) 67 | }); err != nil { 68 | return err 69 | } 70 | return nil 71 | } 72 | 73 | func autoConvert_v1alpha1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(in *ControllerLeaderConfiguration, out *config.ControllerLeaderConfiguration, s conversion.Scope) error { 74 | out.Name = in.Name 75 | out.Component = in.Component 76 | return nil 77 | } 78 | 79 | // Convert_v1alpha1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration is an autogenerated conversion function. 80 | func Convert_v1alpha1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(in *ControllerLeaderConfiguration, out *config.ControllerLeaderConfiguration, s conversion.Scope) error { 81 | return autoConvert_v1alpha1_ControllerLeaderConfiguration_To_config_ControllerLeaderConfiguration(in, out, s) 82 | } 83 | 84 | func autoConvert_config_ControllerLeaderConfiguration_To_v1alpha1_ControllerLeaderConfiguration(in *config.ControllerLeaderConfiguration, out *ControllerLeaderConfiguration, s conversion.Scope) error { 85 | out.Name = in.Name 86 | out.Component = in.Component 87 | return nil 88 | } 89 | 90 | // Convert_config_ControllerLeaderConfiguration_To_v1alpha1_ControllerLeaderConfiguration is an autogenerated conversion function. 91 | func Convert_config_ControllerLeaderConfiguration_To_v1alpha1_ControllerLeaderConfiguration(in *config.ControllerLeaderConfiguration, out *ControllerLeaderConfiguration, s conversion.Scope) error { 92 | return autoConvert_config_ControllerLeaderConfiguration_To_v1alpha1_ControllerLeaderConfiguration(in, out, s) 93 | } 94 | 95 | func autoConvert_v1alpha1_GenericControllerManagerConfiguration_To_config_GenericControllerManagerConfiguration(in *GenericControllerManagerConfiguration, out *config.GenericControllerManagerConfiguration, s conversion.Scope) error { 96 | out.Port = in.Port 97 | out.Address = in.Address 98 | out.MinResyncPeriod = in.MinResyncPeriod 99 | if err := configv1alpha1.Convert_v1alpha1_ClientConnectionConfiguration_To_config_ClientConnectionConfiguration(&in.ClientConnection, &out.ClientConnection, s); err != nil { 100 | return err 101 | } 102 | out.ControllerStartInterval = in.ControllerStartInterval 103 | if err := configv1alpha1.Convert_v1alpha1_LeaderElectionConfiguration_To_config_LeaderElectionConfiguration(&in.LeaderElection, &out.LeaderElection, s); err != nil { 104 | return err 105 | } 106 | out.Controllers = *(*[]string)(unsafe.Pointer(&in.Controllers)) 107 | if err := configv1alpha1.Convert_v1alpha1_DebuggingConfiguration_To_config_DebuggingConfiguration(&in.Debugging, &out.Debugging, s); err != nil { 108 | return err 109 | } 110 | out.LeaderMigrationEnabled = in.LeaderMigrationEnabled 111 | if err := Convert_v1alpha1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(&in.LeaderMigration, &out.LeaderMigration, s); err != nil { 112 | return err 113 | } 114 | return nil 115 | } 116 | 117 | func autoConvert_config_GenericControllerManagerConfiguration_To_v1alpha1_GenericControllerManagerConfiguration(in *config.GenericControllerManagerConfiguration, out *GenericControllerManagerConfiguration, s conversion.Scope) error { 118 | out.Port = in.Port 119 | out.Address = in.Address 120 | out.MinResyncPeriod = in.MinResyncPeriod 121 | if err := configv1alpha1.Convert_config_ClientConnectionConfiguration_To_v1alpha1_ClientConnectionConfiguration(&in.ClientConnection, &out.ClientConnection, s); err != nil { 122 | return err 123 | } 124 | out.ControllerStartInterval = in.ControllerStartInterval 125 | if err := configv1alpha1.Convert_config_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfiguration(&in.LeaderElection, &out.LeaderElection, s); err != nil { 126 | return err 127 | } 128 | out.Controllers = *(*[]string)(unsafe.Pointer(&in.Controllers)) 129 | if err := configv1alpha1.Convert_config_DebuggingConfiguration_To_v1alpha1_DebuggingConfiguration(&in.Debugging, &out.Debugging, s); err != nil { 130 | return err 131 | } 132 | out.LeaderMigrationEnabled = in.LeaderMigrationEnabled 133 | if err := Convert_config_LeaderMigrationConfiguration_To_v1alpha1_LeaderMigrationConfiguration(&in.LeaderMigration, &out.LeaderMigration, s); err != nil { 134 | return err 135 | } 136 | return nil 137 | } 138 | 139 | func autoConvert_v1alpha1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(in *LeaderMigrationConfiguration, out *config.LeaderMigrationConfiguration, s conversion.Scope) error { 140 | out.LeaderName = in.LeaderName 141 | out.ResourceLock = in.ResourceLock 142 | out.ControllerLeaders = *(*[]config.ControllerLeaderConfiguration)(unsafe.Pointer(&in.ControllerLeaders)) 143 | return nil 144 | } 145 | 146 | // Convert_v1alpha1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration is an autogenerated conversion function. 147 | func Convert_v1alpha1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(in *LeaderMigrationConfiguration, out *config.LeaderMigrationConfiguration, s conversion.Scope) error { 148 | return autoConvert_v1alpha1_LeaderMigrationConfiguration_To_config_LeaderMigrationConfiguration(in, out, s) 149 | } 150 | 151 | func autoConvert_config_LeaderMigrationConfiguration_To_v1alpha1_LeaderMigrationConfiguration(in *config.LeaderMigrationConfiguration, out *LeaderMigrationConfiguration, s conversion.Scope) error { 152 | out.LeaderName = in.LeaderName 153 | out.ResourceLock = in.ResourceLock 154 | out.ControllerLeaders = *(*[]ControllerLeaderConfiguration)(unsafe.Pointer(&in.ControllerLeaders)) 155 | return nil 156 | } 157 | 158 | // Convert_config_LeaderMigrationConfiguration_To_v1alpha1_LeaderMigrationConfiguration is an autogenerated conversion function. 159 | func Convert_config_LeaderMigrationConfiguration_To_v1alpha1_LeaderMigrationConfiguration(in *config.LeaderMigrationConfiguration, out *LeaderMigrationConfiguration, s conversion.Scope) error { 160 | return autoConvert_config_LeaderMigrationConfiguration_To_v1alpha1_LeaderMigrationConfiguration(in, out, s) 161 | } 162 | -------------------------------------------------------------------------------- /pkg/clientbuilder/client_builder_dynamic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package clientbuilder 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | "sync" 24 | "time" 25 | 26 | "golang.org/x/oauth2" 27 | v1authenticationapi "k8s.io/api/authentication/v1" 28 | v1 "k8s.io/api/core/v1" 29 | apierrors "k8s.io/apimachinery/pkg/api/errors" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/apimachinery/pkg/util/wait" 32 | apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" 33 | "k8s.io/client-go/discovery" 34 | clientset "k8s.io/client-go/kubernetes" 35 | v1core "k8s.io/client-go/kubernetes/typed/core/v1" 36 | restclient "k8s.io/client-go/rest" 37 | "k8s.io/client-go/transport" 38 | "k8s.io/klog/v2" 39 | "k8s.io/utils/clock" 40 | "k8s.io/utils/ptr" 41 | ) 42 | 43 | var ( 44 | // defaultExpirationSeconds defines the duration of a TokenRequest in seconds. 45 | defaultExpirationSeconds = int64(3600) 46 | // defaultLeewayPercent defines the percentage of expiration left before the client trigger a token rotation. 47 | // range[0, 100] 48 | defaultLeewayPercent = 20 49 | ) 50 | 51 | type DynamicControllerClientBuilder struct { 52 | // ClientConfig is a skeleton config to clone and use as the basis for each controller client 53 | ClientConfig *restclient.Config 54 | 55 | // CoreClient is used to provision service accounts if needed and watch for their associated tokens 56 | // to construct a controller client 57 | CoreClient v1core.CoreV1Interface 58 | 59 | // Namespace is the namespace used to host the service accounts that will back the 60 | // controllers. It must be highly privileged namespace which normal users cannot inspect. 61 | Namespace string 62 | 63 | // roundTripperFuncMap is a cache stores the corresponding roundtripper func for each 64 | // service account 65 | roundTripperFuncMap map[string]func(http.RoundTripper) http.RoundTripper 66 | 67 | // expirationSeconds defines the token expiration seconds 68 | expirationSeconds int64 69 | 70 | // leewayPercent defines the percentage of expiration left before the client trigger a token rotation. 71 | leewayPercent int 72 | 73 | mutex sync.Mutex 74 | 75 | clock clock.Clock 76 | } 77 | 78 | // NewDynamicClientBuilder returns client builder which uses TokenRequest feature and refresh service account token periodically 79 | func NewDynamicClientBuilder(clientConfig *restclient.Config, coreClient v1core.CoreV1Interface, ns string) ControllerClientBuilder { 80 | builder := &DynamicControllerClientBuilder{ 81 | ClientConfig: clientConfig, 82 | CoreClient: coreClient, 83 | Namespace: ns, 84 | roundTripperFuncMap: map[string]func(http.RoundTripper) http.RoundTripper{}, 85 | expirationSeconds: defaultExpirationSeconds, 86 | leewayPercent: defaultLeewayPercent, 87 | clock: clock.RealClock{}, 88 | } 89 | return builder 90 | } 91 | 92 | // this function only for test purpose, don't call it 93 | func NewTestDynamicClientBuilder(clientConfig *restclient.Config, coreClient v1core.CoreV1Interface, ns string, expirationSeconds int64, leewayPercent int) ControllerClientBuilder { 94 | builder := &DynamicControllerClientBuilder{ 95 | ClientConfig: clientConfig, 96 | CoreClient: coreClient, 97 | Namespace: ns, 98 | roundTripperFuncMap: map[string]func(http.RoundTripper) http.RoundTripper{}, 99 | expirationSeconds: expirationSeconds, 100 | leewayPercent: leewayPercent, 101 | clock: clock.RealClock{}, 102 | } 103 | return builder 104 | } 105 | 106 | func (t *DynamicControllerClientBuilder) Config(saName string) (*restclient.Config, error) { 107 | _, err := getOrCreateServiceAccount(t.CoreClient, t.Namespace, saName) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | configCopy := constructClient(t.Namespace, saName, t.ClientConfig) 113 | 114 | t.mutex.Lock() 115 | defer t.mutex.Unlock() 116 | 117 | rt, ok := t.roundTripperFuncMap[saName] 118 | if ok { 119 | configCopy.Wrap(rt) 120 | } else { 121 | cachedTokenSource := transport.NewCachedTokenSource(&tokenSourceImpl{ 122 | namespace: t.Namespace, 123 | serviceAccountName: saName, 124 | coreClient: t.CoreClient, 125 | expirationSeconds: t.expirationSeconds, 126 | leewayPercent: t.leewayPercent, 127 | }) 128 | configCopy.Wrap(transport.ResettableTokenSourceWrapTransport(cachedTokenSource)) 129 | t.roundTripperFuncMap[saName] = configCopy.WrapTransport 130 | } 131 | 132 | return &configCopy, nil 133 | } 134 | 135 | func (t *DynamicControllerClientBuilder) ConfigOrDie(name string) *restclient.Config { 136 | clientConfig, err := t.Config(name) 137 | if err != nil { 138 | klog.Fatal(err) 139 | } 140 | return clientConfig 141 | } 142 | 143 | func (t *DynamicControllerClientBuilder) Client(name string) (clientset.Interface, error) { 144 | clientConfig, err := t.Config(name) 145 | if err != nil { 146 | return nil, err 147 | } 148 | return clientset.NewForConfig(clientConfig) 149 | } 150 | 151 | func (t *DynamicControllerClientBuilder) ClientOrDie(name string) clientset.Interface { 152 | client, err := t.Client(name) 153 | if err != nil { 154 | klog.Fatal(err) 155 | } 156 | return client 157 | } 158 | 159 | func (t *DynamicControllerClientBuilder) DiscoveryClient(name string) (discovery.DiscoveryInterface, error) { 160 | clientConfig, err := t.Config(name) 161 | if err != nil { 162 | return nil, err 163 | } 164 | // Discovery makes a lot of requests infrequently. This allows the burst to succeed and refill to happen 165 | // in just a few seconds. 166 | clientConfig.Burst = 200 167 | clientConfig.QPS = 20 168 | return clientset.NewForConfig(clientConfig) 169 | } 170 | 171 | func (t *DynamicControllerClientBuilder) DiscoveryClientOrDie(name string) discovery.DiscoveryInterface { 172 | client, err := t.DiscoveryClient(name) 173 | if err != nil { 174 | klog.Fatal(err) 175 | } 176 | return client 177 | } 178 | 179 | type tokenSourceImpl struct { 180 | namespace string 181 | serviceAccountName string 182 | coreClient v1core.CoreV1Interface 183 | expirationSeconds int64 184 | leewayPercent int 185 | } 186 | 187 | func (ts *tokenSourceImpl) Token() (*oauth2.Token, error) { 188 | var retTokenRequest *v1authenticationapi.TokenRequest 189 | 190 | backoff := wait.Backoff{ 191 | Duration: 500 * time.Millisecond, 192 | Factor: 2, // double the timeout for every failure 193 | Steps: 4, 194 | } 195 | if err := wait.ExponentialBackoff(backoff, func() (bool, error) { 196 | if _, inErr := getOrCreateServiceAccount(ts.coreClient, ts.namespace, ts.serviceAccountName); inErr != nil { 197 | klog.Warningf("get or create service account failed: %v", inErr) 198 | return false, nil 199 | } 200 | 201 | tr, inErr := ts.coreClient.ServiceAccounts(ts.namespace).CreateToken(context.TODO(), ts.serviceAccountName, &v1authenticationapi.TokenRequest{ 202 | Spec: v1authenticationapi.TokenRequestSpec{ 203 | ExpirationSeconds: ptr.To[int64](ts.expirationSeconds), 204 | }, 205 | }, metav1.CreateOptions{}) 206 | if inErr != nil { 207 | klog.Warningf("get token failed: %v", inErr) 208 | return false, nil 209 | } 210 | retTokenRequest = tr 211 | return true, nil 212 | }); err != nil { 213 | return nil, fmt.Errorf("failed to get token for %s/%s: %v", ts.namespace, ts.serviceAccountName, err) 214 | } 215 | 216 | if retTokenRequest.Spec.ExpirationSeconds == nil { 217 | return nil, fmt.Errorf("nil pointer of expiration in token request") 218 | } 219 | 220 | lifetime := retTokenRequest.Status.ExpirationTimestamp.Time.Sub(time.Now()) 221 | if lifetime < time.Minute*10 { 222 | // possible clock skew issue, pin to minimum token lifetime 223 | lifetime = time.Minute * 10 224 | } 225 | 226 | leeway := time.Duration(int64(lifetime) * int64(ts.leewayPercent) / 100) 227 | expiry := time.Now().Add(lifetime).Add(-1 * leeway) 228 | 229 | return &oauth2.Token{ 230 | AccessToken: retTokenRequest.Status.Token, 231 | TokenType: "Bearer", 232 | Expiry: expiry, 233 | }, nil 234 | } 235 | 236 | func constructClient(saNamespace, saName string, config *restclient.Config) restclient.Config { 237 | username := apiserverserviceaccount.MakeUsername(saNamespace, saName) 238 | // make a shallow copy 239 | // the caller already castrated the config during creation 240 | // this allows for potential extensions in the future 241 | // for example it preserve HTTP wrappers for custom behavior per request 242 | ret := *config 243 | restclient.AddUserAgent(&ret, username) 244 | return ret 245 | } 246 | 247 | func getOrCreateServiceAccount(coreClient v1core.CoreV1Interface, namespace, name string) (*v1.ServiceAccount, error) { 248 | sa, err := coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 249 | if err == nil { 250 | return sa, nil 251 | } 252 | if !apierrors.IsNotFound(err) { 253 | return nil, err 254 | } 255 | 256 | // Create the namespace if we can't verify it exists. 257 | // Tolerate errors, since we don't know whether this component has namespace creation permissions. 258 | if _, err := coreClient.Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}); apierrors.IsNotFound(err) { 259 | if _, err = coreClient.Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { 260 | klog.Warningf("create non-exist namespace %s failed:%v", namespace, err) 261 | } 262 | } 263 | 264 | // Create the service account 265 | sa, err = coreClient.ServiceAccounts(namespace).Create(context.TODO(), &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}}, metav1.CreateOptions{}) 266 | if apierrors.IsAlreadyExists(err) { 267 | // If we're racing to init and someone else already created it, re-fetch 268 | return coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 269 | } 270 | return sa, err 271 | } 272 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= 2 | cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= 3 | github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 4 | github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 5 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= 6 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 7 | github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 8 | github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 9 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 10 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 11 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 12 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 13 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 14 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 15 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 16 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 17 | github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= 18 | github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= 19 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 20 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 21 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 22 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 23 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 27 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 28 | github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= 29 | github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 30 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 31 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 32 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 33 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 34 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 35 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 36 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 37 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 38 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 39 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 40 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 41 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 42 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 43 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 44 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 45 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 46 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 47 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 48 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 49 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 50 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 51 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 52 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 53 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 54 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 55 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 56 | github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 57 | github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 58 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 59 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 60 | github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 61 | github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 62 | github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= 63 | github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= 64 | github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= 65 | github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 66 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 67 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 68 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 69 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= 70 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 71 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 72 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 73 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= 74 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 75 | github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= 76 | github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= 77 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= 78 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= 79 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 80 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 81 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= 82 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= 83 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 84 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 85 | github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= 86 | github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= 87 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 88 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 89 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 90 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 91 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 92 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 93 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 94 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 95 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 96 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 97 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 98 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 99 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 100 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 101 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 102 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 103 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 104 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 105 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 106 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 107 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 108 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 109 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 110 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= 111 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 112 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 113 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 114 | github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= 115 | github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= 116 | github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= 117 | github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= 118 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 119 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 120 | github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 121 | github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 122 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 123 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 124 | github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= 125 | github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= 126 | github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 127 | github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 128 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 129 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 130 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 131 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 132 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 133 | github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 134 | github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= 135 | github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= 136 | github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= 137 | github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 138 | github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= 139 | github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 140 | github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 141 | github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 142 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 143 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 144 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 145 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 146 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 147 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 148 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 149 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 150 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 151 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 152 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 153 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= 154 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= 155 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 156 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 157 | github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= 158 | github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 159 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 160 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 161 | go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= 162 | go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= 163 | go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= 164 | go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= 165 | go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8= 166 | go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk= 167 | go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U= 168 | go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo= 169 | go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM= 170 | go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU= 171 | go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0= 172 | go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0= 173 | go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= 174 | go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= 175 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 176 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 177 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= 178 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= 179 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= 180 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= 181 | go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= 182 | go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= 183 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= 184 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= 185 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= 186 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= 187 | go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= 188 | go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= 189 | go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= 190 | go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= 191 | go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= 192 | go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= 193 | go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= 194 | go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= 195 | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 196 | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 197 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 198 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 199 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 200 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 201 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 202 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 203 | go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= 204 | go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 205 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 206 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 207 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 208 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 209 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 210 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 211 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 212 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 213 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 214 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 215 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 216 | golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= 217 | golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= 218 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 219 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 220 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 221 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 222 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 223 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 224 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 225 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 226 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 227 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 228 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 229 | golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= 230 | golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 231 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 232 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 233 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 234 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 235 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 236 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 237 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 238 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 239 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 240 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 241 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 242 | golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 243 | golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 244 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 245 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 246 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 247 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 248 | golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= 249 | golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= 250 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 251 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 252 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 253 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 254 | google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= 255 | google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= 256 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= 257 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 258 | google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= 259 | google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 260 | google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= 261 | google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 262 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 263 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 264 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 265 | gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= 266 | gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 267 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 268 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 269 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 270 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 271 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 272 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 273 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 274 | k8s.io/api v0.0.0-20251204222646-382014e64b8e h1:pfnI6xGQHUXwuFKumPQjgzDYkBa5RKWJbWfF89vbrS4= 275 | k8s.io/api v0.0.0-20251204222646-382014e64b8e/go.mod h1:OjSG925L2Qod5yd066wDJ6HLaA8lNCJRx5eAtCw5rOM= 276 | k8s.io/apimachinery v0.0.0-20251204222123-56aa7d5cc8bb h1:yb4KUdenOGnO2tWDaMQFR74DE6bnjtSNCE3+InuoO6g= 277 | k8s.io/apimachinery v0.0.0-20251204222123-56aa7d5cc8bb/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= 278 | k8s.io/apiserver v0.0.0-20251204230936-a52843043e97 h1:p96DQe/qskx5aQzoILM0Wh2tsJt6WHr6FEFLOLMVIHw= 279 | k8s.io/apiserver v0.0.0-20251204230936-a52843043e97/go.mod h1:VMcEqTg8U7YRyxW7Su8VU4I+dCLjzAPPAyVR1AeVuag= 280 | k8s.io/client-go v0.0.0-20251204223340-453ad29ccd47 h1:FofVwzAoZ7zcV/l44TiavNHwem1glw6LutItmZNau/Y= 281 | k8s.io/client-go v0.0.0-20251204223340-453ad29ccd47/go.mod h1:6FMz9yhWvi8/eFVsHCRrZnPXd9VO+ZSdz8OmosiP5us= 282 | k8s.io/component-base v0.0.0-20251204225730-8cb15f10375f h1:+E5vv24SeZdDzNIzmNpkWe85rE7nR3pVu+n5mrKSkW4= 283 | k8s.io/component-base v0.0.0-20251204225730-8cb15f10375f/go.mod h1:8Ohmd7Q46U1rnT5I8SNvql3xi+S81vMldMPHMNV+e+I= 284 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 285 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 286 | k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= 287 | k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= 288 | k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= 289 | k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 290 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= 291 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 292 | sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= 293 | sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 294 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 295 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 296 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= 297 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= 298 | sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= 299 | sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= 300 | --------------------------------------------------------------------------------