Skip to content

Commit b35387b

Browse files
committed
Introduce KubeconfigStrategy in cluster-inventory-api provider
Signed-off-by: Shingo Omura <[email protected]>
1 parent 28ad979 commit b35387b

File tree

1 file changed

+76
-67
lines changed

1 file changed

+76
-67
lines changed

providers/cluster-inventory-api/provider.go

Lines changed: 76 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,19 @@ var _ multicluster.Provider = &Provider{}
5454
const (
5555
labelKeyClusterInventoryConsumer = "x-k8s.io/cluster-inventory-consumer"
5656
labelKeyClusterProfile = "x-k8s.io/cluster-profile"
57+
dataKeyKubeConfig = "Config" // data key in the Secret that contains the kubeconfig.
5758
)
5859

60+
// KubeconfigStrategy defines how the kubeconfig for a cluster profile is managed.
61+
// It is used to fetch the kubeconfig for a cluster profile and can be extended to support different strategies.
62+
type KubeconfigStrategy struct {
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.
6071
type 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[dataKeyKubeConfig]
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

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

Comments
 (0)