|  | 
|  | 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