@@ -56,6 +56,17 @@ const (
5656 labelKeyClusterProfile = "x-k8s.io/cluster-profile"
5757)
5858
59+ // KubeconfigStrategy defines how the kubeconfig for a cluster profile is managed.
60+ // It is used to fetch the kubeconfig for a cluster profile and can be extended to support different strategies.
61+ type KubeconfigStrategy struct {
62+
63+ // GetKubeConfig is a function that returns the kubeconfig secret for a cluster profile.
64+ GetKubeConfig func (ctx context.Context , cli client.Client , clp * clusterinventoryv1alpha1.ClusterProfile ) (* rest.Config , error )
65+
66+ // CustomWatches can add custom watches to the provider controller
67+ CustomWatches []CustomWatch
68+ }
69+
5970// Options are the options for the Cluster-API cluster Provider.
6071type Options struct {
6172 // ConsumerName is the name of the consumer that will use the cluster inventory API.
@@ -64,15 +75,17 @@ type Options struct {
6475 // ClusterOptions are the options passed to the cluster constructor.
6576 ClusterOptions []cluster.Option
6677
67- // GetKubeConfig is a function that returns the kubeconfig secret for a cluster profile.
68- GetKubeConfig func (ctx context.Context , cli client.Client , clp * clusterinventoryv1alpha1.ClusterProfile ) (* rest.Config , error )
78+ // KubeconfigStrategy defines how the kubeconfig for the cluster profile is managed.
79+ // It is used to fetch the kubeconfig for a cluster profile and can be extended to support different strategies.
80+ // The default strategy is KubeconfigStrategySecret(consumerName) which fetches the kubeconfig from a Secret
81+ // labeled with "x-k8s.io/cluster-inventory-consumer" and "x-k8s.io/cluster-profile" labels.
82+ // This is the "Push Model via Credentials in Secret" as described in KEP-4322: ClusterProfile API.
83+ // ref: https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/4322-cluster-inventory/README.md#push-model-via-credentials-in-secret-not-recommended
84+ KubeconfigStrategy * KubeconfigStrategy
6985
7086 // NewCluster is a function that creates a new cluster from a rest.Config.
7187 // The cluster will be started by the provider.
7288 NewCluster func (ctx context.Context , clp * clusterinventoryv1alpha1.ClusterProfile , cfg * rest.Config , opts ... cluster.Option ) (cluster.Cluster , error )
73-
74- // CustomWatches can add custom watches to the provider controller
75- CustomWatches []CustomWatch
7689}
7790
7891// CustomWatch specifies a custom watch spec that can be added to the provider controller.
@@ -102,79 +115,75 @@ type Provider struct {
102115 indexers []index
103116}
104117
105- // GetKubeConfigFromSecret returns a function that fetches the kubeconfig for a specified consumer for ClusterProfile from Secret
106- // It supposes that the Secrets for ClusterProfiles are managed by following "Push Model via Credentials in Secret" in "KEP-4322: ClusterProfile API"
118+ // KubeconfigManagementStrategySecret returns a KubeconfigStrategy that fetches the kubeconfig from a Secret
119+ // labeled with "x-k8s.io/cluster-inventory-consumer" and "x-k8s.io/cluster-profile" labels.
120+ // This is the "Push Model via Credentials in Secret" as described in KEP-4322: ClusterProfile API.
107121// ref: https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/4322-cluster-inventory/README.md#push-model-via-credentials-in-secret-not-recommended
108- func GetKubeConfigFromSecret (consumerName string ) func (ctx context.Context , cli client.Client , clp * clusterinventoryv1alpha1.ClusterProfile ) (* rest.Config , error ) {
109- return func (ctx context.Context , cli client.Client , clp * clusterinventoryv1alpha1.ClusterProfile ) (* rest.Config , error ) {
110- secrets := corev1.SecretList {}
111- if err := cli .List (ctx , & secrets , client .InNamespace (clp .Namespace ), client.MatchingLabels {
112- labelKeyClusterInventoryConsumer : consumerName ,
113- labelKeyClusterProfile : clp .Name ,
114- }); err != nil {
115- return nil , fmt .Errorf ("failed to list secrets: %w" , err )
116- }
117-
118- if len (secrets .Items ) == 0 {
119- return nil , fmt .Errorf ("no secrets found" )
120- }
122+ func KubeconfigStrategySecret (consumerName string ) * KubeconfigStrategy {
123+ return & KubeconfigStrategy {
124+ GetKubeConfig : func (ctx context.Context , cli client.Client , clp * clusterinventoryv1alpha1.ClusterProfile ) (* rest.Config , error ) {
125+ secrets := corev1.SecretList {}
126+ if err := cli .List (ctx , & secrets , client .InNamespace (clp .Namespace ), client.MatchingLabels {
127+ labelKeyClusterInventoryConsumer : consumerName ,
128+ labelKeyClusterProfile : clp .Name ,
129+ }); err != nil {
130+ return nil , fmt .Errorf ("failed to list secrets: %w" , err )
131+ }
121132
122- if len (secrets .Items ) > 1 {
123- return nil , fmt .Errorf ("multiple secrets found, expected one, got %d" , len ( secrets . Items ) )
124- }
133+ if len (secrets .Items ) == 0 {
134+ return nil , fmt .Errorf ("no secrets found" )
135+ }
125136
126- secret := secrets .Items [0 ]
137+ if len (secrets .Items ) > 1 {
138+ return nil , fmt .Errorf ("multiple secrets found, expected one, got %d" , len (secrets .Items ))
139+ }
127140
128- data , ok := secret .Data ["Config" ]
129- if ! ok {
130- return nil , fmt .Errorf ("secret %s/%s does not contain Config data" , secret .Namespace , secret .Name )
131- }
132- return clientcmd .RESTConfigFromKubeConfig (data )
133- }
134- }
141+ secret := secrets .Items [0 ]
135142
136- // WatchKubeConfigSecret returns a CustomWatch that watches for kubeconfig secrets for specified consumer of ClusterProfile
137- // It supposes that the Secrets for ClusterProfiles are managed by following "Push Model via Credentials in Secret" in "KEP-4322: ClusterProfile API"
138- // ref: https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/4322-cluster-inventory/README.md#push-model-via-credentials-in-secret-not-recommended
139- func WatchKubeConfigSecret (consumerName string ) CustomWatch {
140- return CustomWatch {
141- Object : & corev1.Secret {},
142- EventHandler : handler .EnqueueRequestsFromMapFunc (func (ctx context.Context , obj client.Object ) []reconcile.Request {
143- secret , ok := obj .(* corev1.Secret )
143+ data , ok := secret .Data ["Config" ]
144144 if ! ok {
145- return nil
145+ return nil , fmt . Errorf ( "secret %s/%s does not contain Config data" , secret . Namespace , secret . Name )
146146 }
147-
148- if secret .GetLabels () == nil ||
149- secret .GetLabels ()[labelKeyClusterInventoryConsumer ] != consumerName ||
150- secret .GetLabels ()[labelKeyClusterProfile ] == "" {
151- return nil
152- }
153-
154- return []reconcile.Request {{
155- NamespacedName : types.NamespacedName {
156- Namespace : secret .GetNamespace (),
157- Name : secret .GetLabels ()[labelKeyClusterProfile ],
158- },
159- }}
160- }),
161- Opts : []builder.WatchesOption {
162- builder .WithPredicates (predicate .NewPredicateFuncs (func (object client.Object ) bool {
163- secret , ok := object .(* corev1.Secret )
147+ return clientcmd .RESTConfigFromKubeConfig (data )
148+ },
149+ CustomWatches : []CustomWatch {CustomWatch {
150+ Object : & corev1.Secret {},
151+ EventHandler : handler .EnqueueRequestsFromMapFunc (func (ctx context.Context , obj client.Object ) []reconcile.Request {
152+ secret , ok := obj .(* corev1.Secret )
164153 if ! ok {
165- return false
154+ return nil
166155 }
167- return secret .GetLabels ()[labelKeyClusterInventoryConsumer ] == consumerName &&
168- secret .GetLabels ()[labelKeyClusterProfile ] != ""
169- })),
170- },
156+
157+ if secret .GetLabels () == nil ||
158+ secret .GetLabels ()[labelKeyClusterInventoryConsumer ] != consumerName ||
159+ secret .GetLabels ()[labelKeyClusterProfile ] == "" {
160+ return nil
161+ }
162+
163+ return []reconcile.Request {{
164+ NamespacedName : types.NamespacedName {
165+ Namespace : secret .GetNamespace (),
166+ Name : secret .GetLabels ()[labelKeyClusterProfile ],
167+ },
168+ }}
169+ }),
170+ Opts : []builder.WatchesOption {
171+ builder .WithPredicates (predicate .NewPredicateFuncs (func (object client.Object ) bool {
172+ secret , ok := object .(* corev1.Secret )
173+ if ! ok {
174+ return false
175+ }
176+ return secret .GetLabels ()[labelKeyClusterInventoryConsumer ] == consumerName &&
177+ secret .GetLabels ()[labelKeyClusterProfile ] != ""
178+ })),
179+ },
180+ }},
171181 }
172182}
173183
174184func setDefaults (opts * Options , cli client.Client ) {
175- if opts .GetKubeConfig == nil {
176- opts .GetKubeConfig = GetKubeConfigFromSecret (opts .ConsumerName )
177- opts .CustomWatches = append (opts .CustomWatches , WatchKubeConfigSecret (opts .ConsumerName ))
185+ if opts .KubeconfigStrategy == nil {
186+ opts .KubeconfigStrategy = KubeconfigStrategySecret (opts .ConsumerName )
178187 }
179188 if opts .NewCluster == nil {
180189 opts .NewCluster = func (ctx context.Context , clp * clusterinventoryv1alpha1.ClusterProfile , cfg * rest.Config , opts ... cluster.Option ) (cluster.Cluster , error ) {
@@ -202,7 +211,7 @@ func New(localMgr manager.Manager, opts Options) (*Provider, error) {
202211 WithOptions (controller.Options {MaxConcurrentReconciles : 1 }) // no parallelism.
203212
204213 // Apply any custom watches provided by the user
205- for _ , customWatch := range p .opts .CustomWatches {
214+ for _ , customWatch := range p .opts .KubeconfigStrategy . CustomWatches {
206215 controllerBuilder .Watches (
207216 customWatch .Object ,
208217 customWatch .EventHandler ,
@@ -286,7 +295,7 @@ func (p *Provider) Reconcile(ctx context.Context, req reconcile.Request) (reconc
286295 }
287296
288297 // get kubeconfig
289- cfg , err := p .opts .GetKubeConfig (ctx , p .client , clp )
298+ cfg , err := p .opts .KubeconfigStrategy . GetKubeConfig (ctx , p .client , clp )
290299 if err != nil {
291300 log .Error (err , "Failed to get kubeconfig for ClusterProfile" )
292301 return reconcile.Result {}, fmt .Errorf ("failed to get kubeconfig for ClusterProfile=%s: %w" , key , err )
0 commit comments