Skip to content

Commit 7a8c909

Browse files
committed
add 'clusters' package to make it easier to work with multiple clusters
1 parent 93671bf commit 7a8c909

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ The `pkg/collections` package contains multiple interfaces for collections, mode
4141

4242
The package also contains further packages that contain some auxiliary functions for working with slices and maps in golang, e.g. for filtering.
4343

44+
## clusters
45+
46+
The `pkg/clusters` package helps with loading kubeconfigs and creating clients for multiple clusters.
47+
```go
48+
foo := clusters.New("foo") // initializes a new cluster with id 'foo'
49+
foo.RegisterConfigPathFlag(cmd.Flags()) // adds a '--foo-cluster' flag to the flag set for passing in a kubeconfig path
50+
foo.InitializeRESTConfig() // loads the kubeconfig using the 'LoadKubeconfig' function from the 'controller' package
51+
foo.InitializeClient(myScheme) // initializes the 'Client' and 'Cluster' interfaces from the controller-runtime
52+
```
53+
You can then use the different getter methods for working with the cluster.
54+
4455
### conditions
4556

4657
The `pkg/conditions` package helps with managing condition lists.

pkg/clusters/cluster.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package clusters
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/openmcp-project/controller-utils/pkg/controller"
7+
flag "github.com/spf13/pflag"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/client-go/rest"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
"sigs.k8s.io/controller-runtime/pkg/cluster"
12+
)
13+
14+
type Cluster struct {
15+
// identifier (for logging purposes only)
16+
id string
17+
// path to kubeconfig
18+
cfgPath string
19+
// cluster config
20+
restCfg *rest.Config
21+
// client
22+
client client.Client
23+
// cluster
24+
cluster cluster.Cluster
25+
}
26+
27+
// Initializes a new cluster.
28+
// Panics if id is empty.
29+
func New(id string) *Cluster {
30+
c := &Cluster{}
31+
c.InitializeID(id)
32+
return c
33+
}
34+
35+
// WithConfigPath sets the config path for the cluster.
36+
// Returns the cluster for chaining.
37+
func (c *Cluster) WithConfigPath(cfgPath string) *Cluster {
38+
c.cfgPath = cfgPath
39+
return c
40+
}
41+
42+
// RegisterConfigPathFlag adds a flag '--<id>-cluster' for the cluster's config path to the given flag set.
43+
// Panics if the cluster's id is not set.
44+
func (c *Cluster) RegisterConfigPathFlag(flags *flag.FlagSet) {
45+
if !c.HasID() {
46+
panic("cluster id must be set before registering the config path flag")
47+
}
48+
flags.StringVar(&c.cfgPath, fmt.Sprintf("%s-cluster", c.id), "", fmt.Sprintf("Path to the %s cluster kubeconfig file or directory containing either a kubeconfig or host, token, and ca file. Leave empty to use in-cluster config.", c.id))
49+
}
50+
51+
///////////////////
52+
// STATUS CHECKS //
53+
///////////////////
54+
55+
// HasID returns true if the cluster has an id.
56+
// If this returns false, initialize a new cluster via New() or InitializeID().
57+
func (c *Cluster) HasID() bool {
58+
return c != nil && c.id != ""
59+
}
60+
61+
// HasRESTConfig returns true if the cluster has a REST config.
62+
// If this returns false, load the config via InitializeRESTConfig().
63+
func (c *Cluster) HasRESTConfig() bool {
64+
return c != nil && c.restCfg != nil
65+
}
66+
67+
// HasClient returns true if the cluster has a client.
68+
// If this returns false, create a client via InitializeClient().
69+
func (c *Cluster) HasClient() bool {
70+
return c != nil && c.client != nil
71+
}
72+
73+
//////////////////
74+
// INITIALIZERS //
75+
//////////////////
76+
77+
// InitializeID sets the cluster's id.
78+
// Panics if id is empty.
79+
func (c *Cluster) InitializeID(id string) {
80+
if id == "" {
81+
panic("id must not be empty")
82+
}
83+
c.id = id
84+
}
85+
86+
// InitializeRESTConfig loads the cluster's REST config.
87+
// If the config has already been loaded, this is a no-op.
88+
// Panics if the cluster's id is not set (InitializeID must be called first).
89+
func (c *Cluster) InitializeRESTConfig() error {
90+
if !c.HasID() {
91+
panic("cluster id must be set before loading the config")
92+
}
93+
if c.HasRESTConfig() {
94+
return nil
95+
}
96+
cfg, err := controller.LoadKubeconfig(c.cfgPath)
97+
if err != nil {
98+
return fmt.Errorf("failed to load '%s' cluster kubeconfig: %w", c.ID(), err)
99+
}
100+
c.restCfg = cfg
101+
return nil
102+
}
103+
104+
// InitializeClient creates a new client for the cluster.
105+
// This also initializes the cluster's controller-runtime 'Cluster' representation.
106+
// If the client has already been initialized, this is a no-op.
107+
// Panics if the cluster's REST config has not been loaded (InitializeRESTConfig must be called first).
108+
func (c *Cluster) InitializeClient(scheme *runtime.Scheme) error {
109+
if !c.HasRESTConfig() {
110+
panic("cluster REST config must be set before creating the client")
111+
}
112+
if c.HasClient() {
113+
return nil
114+
}
115+
cli, err := client.New(c.restCfg, client.Options{Scheme: scheme})
116+
if err != nil {
117+
return fmt.Errorf("failed to create '%s' cluster client: %w", c.ID(), err)
118+
}
119+
clu, err := cluster.New(c.restCfg, func(o *cluster.Options) { o.Scheme = scheme })
120+
if err != nil {
121+
return fmt.Errorf("failed to create '%s' cluster Cluster representation: %w", c.ID(), err)
122+
}
123+
c.client = cli
124+
c.cluster = clu
125+
return nil
126+
}
127+
128+
/////////////
129+
// GETTERS //
130+
/////////////
131+
132+
// ID returns the cluster's id.
133+
func (c *Cluster) ID() string {
134+
return c.id
135+
}
136+
137+
// ConfigPath returns the cluster's config path.
138+
func (c *Cluster) ConfigPath() string {
139+
return c.cfgPath
140+
}
141+
142+
// RESTConfig returns the cluster's REST config.
143+
// This returns a pointer, but modification can lead to inconsistent behavior and is not recommended.
144+
func (c *Cluster) RESTConfig() *rest.Config {
145+
return c.restCfg
146+
}
147+
148+
// Client returns the cluster's client.
149+
func (c *Cluster) Client() client.Client {
150+
return c.client
151+
}
152+
153+
// Cluster returns the cluster's controller-runtime 'Cluster' representation.
154+
func (c *Cluster) Cluster() cluster.Cluster {
155+
return c.cluster
156+
}
157+
158+
// Scheme returns the cluster's scheme.
159+
// Returns nil if the client has not been initialized.
160+
func (c *Cluster) Scheme() *runtime.Scheme {
161+
if c.cluster == nil {
162+
return nil
163+
}
164+
return c.cluster.GetScheme()
165+
}
166+
167+
// APIServerEndpoint returns the cluster's API server endpoint.
168+
// Returns an empty string if the REST config has not been initialized.
169+
func (c *Cluster) APIServerEndpoint() string {
170+
if c.restCfg == nil {
171+
return ""
172+
}
173+
return c.restCfg.Host
174+
}

0 commit comments

Comments
 (0)