Skip to content

Commit f8e0f0d

Browse files
committed
operator: support kubeconfig key and custom keys in convertKubeConfigFrom
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
1 parent 2683264 commit f8e0f0d

3 files changed

Lines changed: 364 additions & 11 deletions

File tree

docs/api/v1/resourceset.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,65 @@ setting the `--watch-configs-label-selector=owner!=helm`
502502
flag in flux-operator, which allows watching all ConfigMaps
503503
and Secrets except for Helm storage Secrets.
504504

505+
#### Converting kubeconfig data from Secrets
506+
507+
To generate ConfigMap resources with the API server address and CA certificate
508+
extracted from a kubeconfig stored in a Secret, the
509+
`fluxcd.controlplane.io/convertKubeConfigFrom` annotation must be set on the
510+
ConfigMap template.
511+
512+
The annotation value must be in the format `namespace/name` or `namespace/name:key`:
513+
514+
- `namespace/name` - looks for the kubeconfig data under the `kubeconfig` key first,
515+
then falls back to the `value` key in the referenced Secret.
516+
- `namespace/name:key` - uses the specified custom key to read the kubeconfig data
517+
from the referenced Secret.
518+
519+
Example of converting kubeconfig data from a CAPI Secret:
520+
521+
```yaml
522+
spec:
523+
inputs:
524+
- cluster: "staging"
525+
- cluster: "production"
526+
resources:
527+
- apiVersion: v1
528+
kind: ConfigMap
529+
metadata:
530+
name: << inputs.cluster >>-cluster-info
531+
namespace: flux-system
532+
annotations:
533+
fluxcd.controlplane.io/convertKubeConfigFrom: "flux-system/<< inputs.cluster >>-kubeconfig"
534+
data:
535+
cluster: << inputs.cluster >>
536+
```
537+
538+
In the above example, the operator reads the kubeconfig from the
539+
`flux-system/<cluster>-kubeconfig` Secret, extracts the API server address
540+
and the CA certificate from the first cluster entry, and populates the
541+
`address` and `ca.crt` fields in the generated ConfigMap.
542+
543+
If the ConfigMap template already contains `address` or `ca.crt` fields,
544+
the existing values are preserved and not overwritten.
545+
546+
Example using a custom Secret key:
547+
548+
```yaml
549+
spec:
550+
inputs:
551+
- cluster: "staging"
552+
resources:
553+
- apiVersion: v1
554+
kind: ConfigMap
555+
metadata:
556+
name: << inputs.cluster >>-cluster-info
557+
namespace: flux-system
558+
annotations:
559+
fluxcd.controlplane.io/convertKubeConfigFrom: "flux-system/<< inputs.cluster >>-kubeconfig:my-custom-key"
560+
data:
561+
cluster: << inputs.cluster >>
562+
```
563+
505564
#### Conditional resource exclusion
506565

507566
To exclude a resource based on input values, the `fluxcd.controlplane.io/reconcile` annotation can be set

internal/controller/resourceset_controller.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,8 @@ func (r *ResourceSetReconciler) copyResources(ctx context.Context,
689689
// convertKubeConfigResources converts kubeconfig data stored in Secrets
690690
// into ConfigMap fields by extracting the server and CA certificate.
691691
// The conversion is triggered using a specific annotation on the ConfigMap.
692+
// The annotation value must be in the format 'namespace/name' or 'namespace/name:key'.
693+
// When no key is specified, the function looks for 'kubeconfig' first, then 'value'.
692694
func (r *ResourceSetReconciler) convertKubeConfigResources(
693695
ctx context.Context,
694696
kubeClient client.Client,
@@ -705,9 +707,20 @@ func (r *ResourceSetReconciler) convertKubeConfigResources(
705707
continue
706708
}
707709

708-
sourceParts := strings.Split(source, "/")
710+
// Parse the annotation value to extract namespace/name and optional key.
711+
// Supported formats: 'namespace/name' or 'namespace/name:key'.
712+
var customKey string
713+
nameRef := source
714+
if colonIdx := strings.LastIndex(source, ":"); colonIdx > 0 {
715+
if slashIdx := strings.Index(source, "/"); slashIdx > 0 && colonIdx > slashIdx {
716+
customKey = source[colonIdx+1:]
717+
nameRef = source[:colonIdx]
718+
}
719+
}
720+
721+
sourceParts := strings.Split(nameRef, "/")
709722
if len(sourceParts) != 2 {
710-
return fmt.Errorf("invalid %s annotation value '%s' must be in the format 'namespace/name'", fluxcdv1.ConvertKubeConfigFromAnnotation, source)
723+
return fmt.Errorf("invalid %s annotation value '%s' must be in the format 'namespace/name' or 'namespace/name:key'", fluxcdv1.ConvertKubeConfigFromAnnotation, source)
711724
}
712725

713726
sourceName := types.NamespacedName{
@@ -717,12 +730,24 @@ func (r *ResourceSetReconciler) convertKubeConfigResources(
717730

718731
secret := &corev1.Secret{}
719732
if err := kubeClient.Get(ctx, sourceName, secret); err != nil {
720-
return fmt.Errorf("failed to get kubeconfig Secret/%s: %w", source, err)
733+
return fmt.Errorf("failed to get kubeconfig Secret/%s: %w", nameRef, err)
721734
}
722735

723-
data, exists := secret.Data["value"]
724-
if !exists {
725-
return fmt.Errorf("kubeconfig Secret/%s does not have 'value' field", source)
736+
var data []byte
737+
var exists bool
738+
if customKey != "" {
739+
data, exists = secret.Data[customKey]
740+
if !exists {
741+
return fmt.Errorf("kubeconfig Secret/%s does not have '%s' field", nameRef, customKey)
742+
}
743+
} else {
744+
data, exists = secret.Data["kubeconfig"]
745+
if !exists {
746+
data, exists = secret.Data["value"]
747+
}
748+
if !exists {
749+
return fmt.Errorf("kubeconfig Secret/%s does not have 'kubeconfig' or 'value' field", nameRef)
750+
}
726751
}
727752

728753
kubeconfigYAML := string(data)

0 commit comments

Comments
 (0)