Skip to content

Commit e3bec98

Browse files
authored
feat: cluster info provider for version checking (#104)
1 parent 8fc0923 commit e3bec98

File tree

4 files changed

+284
-18
lines changed

4 files changed

+284
-18
lines changed

cmd/main.go

+26-18
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
// to ensure that exec-entrypoint and run can make use of them.
2525

2626
_ "k8s.io/client-go/plugin/pkg/client/auth"
27-
"k8s.io/client-go/rest"
2827

2928
"k8s.io/apimachinery/pkg/runtime"
3029
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -72,19 +71,21 @@ func main() {
7271
metrics.Register()
7372
restConfig := ctrl.GetConfigOrDie()
7473

74+
clusterInfo, err := util.NewClusterInfoFromConfig(context.Background(), restConfig)
75+
if err != nil {
76+
setupLog.Error(err, "failed to create cluster info provider")
77+
os.Exit(1)
78+
}
79+
80+
versionInfo, err := clusterInfo.GetClusterVersion()
81+
if err != nil {
82+
setupLog.Error(err, "failed to get cluster version, continuing...")
83+
} else {
84+
setupLog.Info("cluster info", "version", versionInfo.GitVersion)
85+
}
86+
7587
if cfg.ValidateFeatureEnabled {
76-
setupLog.Info("validating required feature gates")
77-
featureGates, err := getFeatureGatesFromMetrics(context.Background(), restConfig)
78-
if err == nil {
79-
if !featureGates.IsEnabledAnyStage(InPlacePodVerticalScalingFeatureGateName) {
80-
setupLog.Error(
81-
fmt.Errorf("%s is not enabled at any stage", InPlacePodVerticalScalingFeatureGateName),
82-
"required feature gates are not enabled")
83-
os.Exit(1)
84-
}
85-
} else {
86-
setupLog.Error(err, "failed to validate required feature gates, continuing...")
87-
}
88+
validateFeatureGates(clusterInfo)
8889
}
8990

9091
tlsOpts := []func(*tls.Config){}
@@ -169,10 +170,17 @@ func setupControllers(mgr ctrl.Manager, boostMgr boost.Manager, cfg *config.Conf
169170
//+kubebuilder:scaffold:builder
170171
}
171172

172-
func getFeatureGatesFromMetrics(ctx context.Context, cfg *rest.Config) (util.FeatureGates, error) {
173-
fgValidator, err := util.NewMetricsFeatureGateValidatorFromConfig(ctx, cfg)
174-
if err != nil {
175-
return nil, err
173+
func validateFeatureGates(clusterInfo util.ClusterInfo) {
174+
setupLog.Info("validating required feature gates")
175+
featureGates, err := clusterInfo.GetFeatureGates()
176+
if err == nil {
177+
if !featureGates.IsEnabledAnyStage(InPlacePodVerticalScalingFeatureGateName) {
178+
setupLog.Error(
179+
fmt.Errorf("%s is not enabled at any stage", InPlacePodVerticalScalingFeatureGateName),
180+
"required feature gates are not enabled")
181+
os.Exit(1)
182+
}
183+
} else {
184+
setupLog.Error(err, "failed to validate required feature gates, continuing...")
176185
}
177-
return fgValidator.GetFeatureGates()
178186
}

internal/mock/featuregatevalidator.go

+69
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/util/cluster_info.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package util
16+
17+
import (
18+
"context"
19+
20+
"k8s.io/apimachinery/pkg/version"
21+
"k8s.io/client-go/kubernetes"
22+
restclient "k8s.io/client-go/rest"
23+
)
24+
25+
// ClusterInfo provides information about the cluster
26+
type ClusterInfo interface {
27+
//GetClusterVersion returns the cluster version
28+
GetClusterVersion() (*version.Info, error)
29+
// GetFeatureGates returns the supported feature gates
30+
GetFeatureGates() (FeatureGates, error)
31+
}
32+
33+
// clusterInfo implements ClusterInfo with a discovery client
34+
type clusterInfo struct {
35+
ctx context.Context
36+
client kubernetes.Interface
37+
clusterVersion *version.Info
38+
fgValidator FeatureGateValidator
39+
}
40+
41+
func NewClusterInfoFromConfig(ctx context.Context, config *restclient.Config) (ClusterInfo, error) {
42+
client, err := kubernetes.NewForConfig(config)
43+
if err != nil {
44+
return nil, err
45+
}
46+
fgValidator := NewMetricsFeatureGateValidator(ctx, client.RESTClient())
47+
return NewClusterInfo(ctx, client, fgValidator), nil
48+
}
49+
50+
func NewClusterInfo(ctx context.Context, client kubernetes.Interface,
51+
fgValidator FeatureGateValidator) ClusterInfo {
52+
return &clusterInfo{
53+
ctx: ctx,
54+
client: client,
55+
fgValidator: fgValidator,
56+
}
57+
}
58+
59+
func (c *clusterInfo) GetClusterVersion() (*version.Info, error) {
60+
if c.clusterVersion != nil {
61+
return c.clusterVersion, nil
62+
}
63+
clusterVersion, err := c.client.Discovery().ServerVersion()
64+
if err != nil {
65+
return nil, err
66+
}
67+
c.clusterVersion = clusterVersion
68+
return clusterVersion, nil
69+
}
70+
71+
func (c *clusterInfo) GetFeatureGates() (FeatureGates, error) {
72+
return c.fgValidator.GetFeatureGates()
73+
}

internal/util/cluster_info_test.go

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package util_test
16+
17+
import (
18+
"context"
19+
"errors"
20+
21+
"github.com/google/kube-startup-cpu-boost/internal/mock"
22+
"github.com/google/kube-startup-cpu-boost/internal/util"
23+
. "github.com/onsi/ginkgo/v2"
24+
. "github.com/onsi/gomega"
25+
"go.uber.org/mock/gomock"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/version"
28+
fakediscovery "k8s.io/client-go/discovery/fake"
29+
"k8s.io/client-go/kubernetes"
30+
fakeclientset "k8s.io/client-go/kubernetes/fake"
31+
kubetesting "k8s.io/client-go/testing"
32+
)
33+
34+
var _ = Describe("Cluster info", func() {
35+
var (
36+
clusterInfo util.ClusterInfo
37+
)
38+
Describe("retrieves cluster version)", func() {
39+
var (
40+
fakeClientSet kubernetes.Interface
41+
versionInfo *version.Info
42+
err error
43+
)
44+
JustBeforeEach(func() {
45+
clusterInfo = util.NewClusterInfo(context.TODO(), fakeClientSet, nil)
46+
versionInfo, err = clusterInfo.GetClusterVersion()
47+
})
48+
When("discovery client errors", func() {
49+
BeforeEach(func() {
50+
fakeClientSet = fakeclientset.NewSimpleClientset()
51+
fakeClientSet.Discovery().(*fakediscovery.FakeDiscovery).
52+
PrependReactor("*", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
53+
return true, nil, errors.New("test error")
54+
})
55+
})
56+
It("errors", func() {
57+
Expect(err).To(HaveOccurred())
58+
})
59+
})
60+
When("discovery client returns server version", func() {
61+
BeforeEach(func() {
62+
fakeClientSet = fakeclientset.NewSimpleClientset()
63+
fakeClientSet.Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion =
64+
&version.Info{
65+
GitVersion: "1.1.1",
66+
}
67+
})
68+
It("doesn't error", func() {
69+
Expect(err).NotTo(HaveOccurred())
70+
})
71+
It("returns valid version info", func() {
72+
Expect(versionInfo.GitVersion).To(Equal("1.1.1"))
73+
})
74+
})
75+
})
76+
Describe("retrieves feature gates", func() {
77+
var (
78+
mockCtrl *gomock.Controller
79+
mockFgValidator *mock.MockFeatureGateValidator
80+
featureGates util.FeatureGates
81+
err error
82+
)
83+
BeforeEach(func() {
84+
mockCtrl = gomock.NewController(GinkgoT())
85+
mockFgValidator = mock.NewMockFeatureGateValidator(mockCtrl)
86+
})
87+
JustBeforeEach(func() {
88+
clusterInfo = util.NewClusterInfo(context.TODO(), nil, mockFgValidator)
89+
featureGates, err = clusterInfo.GetFeatureGates()
90+
})
91+
When("feature gate validator errors", func() {
92+
BeforeEach(func() {
93+
mockFgValidator.EXPECT().GetFeatureGates().Return(nil, errors.New("test error"))
94+
})
95+
It("errors", func() {
96+
Expect(err).To(HaveOccurred())
97+
})
98+
})
99+
When("feature gate validator returns feature gates", func() {
100+
var (
101+
expectedFeatureGates util.FeatureGates
102+
)
103+
BeforeEach(func() {
104+
expectedFeatureGates = util.FeatureGates{
105+
"testFeature": {"testStage": true}}
106+
mockFgValidator.EXPECT().GetFeatureGates().Return(expectedFeatureGates, nil)
107+
})
108+
It("doesn't error", func() {
109+
Expect(err).NotTo(HaveOccurred())
110+
})
111+
It("returns valid feature gates", func() {
112+
Expect(featureGates).To(Equal(expectedFeatureGates))
113+
})
114+
})
115+
})
116+
})

0 commit comments

Comments
 (0)