Skip to content

Commit c706b3d

Browse files
authored
fix: preserve registry addon root CA on move (#1155)
**What problem does this PR solve?**: In the initial [PR](#1127) I misunderstood how clusterctl move handles Secrets. It only moves the Secrets in infra provider namespace and not the CAREN namespace https://cluster-api.sigs.k8s.io/developer/providers/contracts/clusterctl#move. This results in the root CA being recreated and the management cluster ends up using a different CA for the registry addon as compared to the workload clusters. **Which issue(s) this PR fixes**: Fixes # **How Has This Been Tested?**: <!-- Please describe the tests that you ran to verify your changes. Provide output from the tests and any manual steps needed to replicate the tests. --> - Tested manually to verify that I can still push images. - Added unit, integration and e2e tests. **Special notes for your reviewer**: <!-- Use this to provide any additional information to the reviewers. This may include: - Best way to review the PR. - Where the author wants the most review attention on. - etc. -->
1 parent 21d4c88 commit c706b3d

File tree

11 files changed

+626
-36
lines changed

11 files changed

+626
-36
lines changed

charts/cluster-api-runtime-extensions-nutanix/templates/certificates.yaml

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,3 @@ spec:
3232
kind: {{ .Values.certificates.issuer.kind }}
3333
name: {{ template "chart.issuerName" . }}
3434
secretName: {{ template "chart.name" . }}-admission-tls
35-
---
36-
# CA used to sign certificates for the clusters' registry addons
37-
apiVersion: cert-manager.io/v1
38-
kind: Certificate
39-
metadata:
40-
name: registry-addon-root-ca
41-
namespace: {{ .Release.Namespace }}
42-
labels:
43-
{{- include "chart.labels" . | nindent 4 }}
44-
spec:
45-
isCA: true
46-
commonName: registry-addon
47-
secretName: registry-addon-root-ca
48-
issuerRef:
49-
kind: {{ .Values.certificates.issuer.kind }}
50-
name: {{ template "chart.issuerName" . }}
51-
duration: 87600h # 10 years

common/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
k8s.io/apiextensions-apiserver v0.32.6
2121
k8s.io/apimachinery v0.32.6
2222
k8s.io/apiserver v0.32.6
23+
k8s.io/client-go v0.32.6
2324
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
2425
sigs.k8s.io/cluster-api v1.10.3
2526
sigs.k8s.io/cluster-api/test v1.10.3
@@ -86,7 +87,6 @@ require (
8687
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
8788
gopkg.in/inf.v0 v0.9.1 // indirect
8889
gopkg.in/yaml.v3 v3.0.1 // indirect
89-
k8s.io/client-go v0.32.6 // indirect
9090
k8s.io/cluster-bootstrap v0.32.3 // indirect
9191
k8s.io/component-base v0.32.6 // indirect
9292
k8s.io/klog/v2 v2.130.1 // indirect

common/pkg/capi/utils/utils.go

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,110 @@ import (
1515
)
1616

1717
// ManagementCluster returns a Cluster object if c is pointing to a management cluster, otherwise returns nil.
18-
func ManagementCluster(ctx context.Context, c client.Client) (*clusterv1.Cluster, error) {
19-
allNodes := &corev1.NodeList{}
20-
err := c.List(ctx, allNodes)
18+
func ManagementCluster(ctx context.Context, c client.Reader) (*clusterv1.Cluster, error) {
19+
clusterName, clusterNamespace, err := clusterAnnotationsFromNodes(ctx, c)
2120
if err != nil {
22-
return nil, fmt.Errorf("error listing Nodes: %w", err)
23-
}
24-
if len(allNodes.Items) == 0 {
25-
return nil, nil
21+
return nil, fmt.Errorf("error getting cluster annotations from Nodes: %w", err)
2622
}
27-
annotations := allNodes.Items[0].Annotations
28-
clusterName := annotations[clusterv1.ClusterNameAnnotation]
29-
clusterNamespace := annotations[clusterv1.ClusterNamespaceAnnotation]
23+
3024
if clusterName == "" && clusterNamespace == "" {
3125
return nil, nil
3226
}
3327

28+
cluster, err := managementClusterFromNodeAnnotations(ctx, c, clusterName, clusterNamespace)
29+
if err != nil {
30+
if k8serrors.IsNotFound(err) || meta.IsNoMatchError(err) {
31+
return nil, nil
32+
}
33+
return nil, err
34+
}
35+
36+
return cluster, nil
37+
}
38+
39+
// ManagementOrFutureManagementCluster returns a Cluster object to either the management cluster,
40+
// when c is a client to the management cluster.
41+
// Or a cluster that is assumed to become the management cluster in the future,
42+
// when is c is a client to a bootstrap cluster and there is only a single Cluster object.
43+
func ManagementOrFutureManagementCluster(ctx context.Context, c client.Reader) (*clusterv1.Cluster, error) {
44+
clusterName, clusterNamespace, err := clusterAnnotationsFromNodes(ctx, c)
45+
if err != nil {
46+
return nil, fmt.Errorf("error getting cluster annotations from Nodes: %w", err)
47+
}
48+
49+
var cluster *clusterv1.Cluster
50+
switch {
51+
case clusterName != "" && clusterNamespace != "":
52+
cluster, err = managementClusterFromNodeAnnotations(ctx, c, clusterName, clusterNamespace)
53+
case clusterName == "" && clusterNamespace == "":
54+
cluster, err = managementClusterFromBootstrapClient(ctx, c)
55+
case clusterName == "":
56+
err = fmt.Errorf("missing %q annotation", clusterv1.ClusterNameAnnotation)
57+
case clusterNamespace == "":
58+
err = fmt.Errorf("missing %q annotation", clusterv1.ClusterNamespaceAnnotation)
59+
}
60+
if err != nil {
61+
return nil, fmt.Errorf("error determining management cluster for the provided client: %w", err)
62+
}
63+
64+
return cluster, nil
65+
}
66+
67+
func clusterAnnotationsFromNodes(ctx context.Context, c client.Reader) (string, string, error) {
68+
allNodes := &corev1.NodeList{}
69+
err := c.List(ctx, allNodes)
70+
if err != nil {
71+
return "", "", fmt.Errorf("error listing Nodes: %w", err)
72+
}
73+
74+
// Get node annotations that should exist in the management cluster.
75+
annotations := make(map[string]string)
76+
if len(allNodes.Items) > 0 {
77+
annotations = allNodes.Items[0].Annotations
78+
}
79+
return annotations[clusterv1.ClusterNameAnnotation], annotations[clusterv1.ClusterNamespaceAnnotation], nil
80+
}
81+
82+
func managementClusterFromNodeAnnotations(
83+
ctx context.Context,
84+
c client.Reader,
85+
clusterName, clusterNamespace string,
86+
) (*clusterv1.Cluster, error) {
3487
cluster := &clusterv1.Cluster{}
3588
key := client.ObjectKey{
3689
Name: clusterName,
3790
Namespace: clusterNamespace,
3891
}
39-
err = c.Get(ctx, key, cluster)
92+
err := c.Get(ctx, key, cluster)
4093
if err != nil {
41-
if k8serrors.IsNotFound(err) || meta.IsNoMatchError(err) {
42-
return nil, nil
43-
}
4494
return nil, fmt.Errorf("error getting Cluster object based on Node annotations: %w", err)
4595
}
4696

4797
return cluster, nil
4898
}
4999

100+
// managementClusterFromBootstrapClient returns a Cluster object that is assumed to become the management cluster.
101+
// Returns an error if there is not exactly one Cluster object in the cluster.
102+
func managementClusterFromBootstrapClient(
103+
ctx context.Context,
104+
c client.Reader,
105+
) (*clusterv1.Cluster, error) {
106+
clusters := &clusterv1.ClusterList{}
107+
err := c.List(ctx, clusters)
108+
if err != nil {
109+
return nil, fmt.Errorf("error listing Clusters: %w", err)
110+
}
111+
112+
switch {
113+
case len(clusters.Items) == 0:
114+
return nil, fmt.Errorf("no Cluster objects found")
115+
case len(clusters.Items) > 1:
116+
return nil, fmt.Errorf("multiple Cluster objects found, expected exactly one")
117+
default:
118+
return &clusters.Items[0], nil
119+
}
120+
}
121+
50122
func GetProvider(cluster *clusterv1.Cluster) string {
51123
if cluster == nil {
52124
return ""

0 commit comments

Comments
 (0)