Skip to content

Commit d55b832

Browse files
authored
Merge pull request #6673 from sbueringer/pr-improve-controlplaneendpoint-handling
🐛 ClusterClass: reconcile InfrastructureCluster controlPlaneEndpoint
2 parents 0460a26 + 2fc64e8 commit d55b832

File tree

3 files changed

+221
-14
lines changed

3 files changed

+221
-14
lines changed

internal/contract/infrastructure_cluster.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ limitations under the License.
1616

1717
package contract
1818

19-
import "sync"
19+
import (
20+
"sync"
21+
22+
"github.com/pkg/errors"
23+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
24+
)
2025

2126
// InfrastructureClusterContract encodes information about the Cluster API contract for InfrastructureCluster objects
2227
// like e.g the DockerCluster, AWS Cluster etc.
@@ -33,11 +38,52 @@ func InfrastructureCluster() *InfrastructureClusterContract {
3338
return infrastructureCluster
3439
}
3540

36-
// IgnorePaths returns a list of paths to be ignored when reconciling a topology.
37-
func (c *InfrastructureClusterContract) IgnorePaths() []Path {
38-
return []Path{
39-
// NOTE: the controlPlaneEndpoint struct currently contains two mandatory fields (host and port); without this
40-
// ignore path they are going to be always reconciled to the default value or to the value set into the template.
41-
{"spec", "controlPlaneEndpoint"},
41+
// ControlPlaneEndpoint provides access to ControlPlaneEndpoint in an InfrastructureCluster object.
42+
func (c *InfrastructureClusterContract) ControlPlaneEndpoint() *InfrastructureClusterControlPlaneEndpoint {
43+
return &InfrastructureClusterControlPlaneEndpoint{}
44+
}
45+
46+
// InfrastructureClusterControlPlaneEndpoint provides a helper struct for working with ControlPlaneEndpoint
47+
// in an InfrastructureCluster object.
48+
type InfrastructureClusterControlPlaneEndpoint struct{}
49+
50+
// Host provides access to the host field in the ControlPlaneEndpoint in an InfrastructureCluster object.
51+
func (c *InfrastructureClusterControlPlaneEndpoint) Host() *String {
52+
return &String{
53+
path: []string{"spec", "controlPlaneEndpoint", "host"},
54+
}
55+
}
56+
57+
// Port provides access to the port field in the ControlPlaneEndpoint in an InfrastructureCluster object.
58+
func (c *InfrastructureClusterControlPlaneEndpoint) Port() *Int64 {
59+
return &Int64{
60+
path: []string{"spec", "controlPlaneEndpoint", "port"},
61+
}
62+
}
63+
64+
// IgnorePaths returns a list of paths to be ignored when reconciling an InfrastructureCluster.
65+
// NOTE: The controlPlaneEndpoint struct currently contains two mandatory fields (host and port).
66+
// As the host and port fields are not using omitempty, they are automatically set to their zero values
67+
// if they are not set by the user. We don't want to reconcile the zero values as we would then overwrite
68+
// changes applied by the infrastructure provider controller.
69+
func (c *InfrastructureClusterContract) IgnorePaths(infrastructureCluster *unstructured.Unstructured) ([]Path, error) {
70+
var ignorePaths []Path
71+
72+
host, ok, err := unstructured.NestedString(infrastructureCluster.UnstructuredContent(), InfrastructureCluster().ControlPlaneEndpoint().Host().Path()...)
73+
if err != nil {
74+
return nil, errors.Wrapf(err, "failed to retrieve %s", InfrastructureCluster().ControlPlaneEndpoint().Host().Path().String())
75+
}
76+
if ok && host == "" {
77+
ignorePaths = append(ignorePaths, InfrastructureCluster().ControlPlaneEndpoint().Host().Path())
78+
}
79+
80+
port, ok, err := unstructured.NestedInt64(infrastructureCluster.UnstructuredContent(), InfrastructureCluster().ControlPlaneEndpoint().Port().Path()...)
81+
if err != nil {
82+
return nil, errors.Wrapf(err, "failed to retrieve %s", InfrastructureCluster().ControlPlaneEndpoint().Port().Path().String())
4283
}
84+
if ok && port == 0 {
85+
ignorePaths = append(ignorePaths, InfrastructureCluster().ControlPlaneEndpoint().Port().Path())
86+
}
87+
88+
return ignorePaths, nil
4389
}

internal/contract/infrastructure_cluster_test.go

Lines changed: 161 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,168 @@ import (
2020
"testing"
2121

2222
. "github.com/onsi/gomega"
23+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2324
)
2425

2526
func TestInfrastructureCluster(t *testing.T) {
26-
t.Run("Has ignore paths", func(t *testing.T) {
27-
g := NewWithT(t)
28-
g.Expect(InfrastructureCluster().IgnorePaths()).To(Equal([]Path{
29-
{"spec", "controlPlaneEndpoint"},
30-
}))
31-
})
27+
tests := []struct {
28+
name string
29+
infrastructureCluster *unstructured.Unstructured
30+
want []Path
31+
expectErr bool
32+
}{
33+
{
34+
name: "No ignore paths when controlPlaneEndpoint is not set",
35+
infrastructureCluster: &unstructured.Unstructured{
36+
Object: map[string]interface{}{
37+
"spec": map[string]interface{}{
38+
"server": "1.2.3.4",
39+
},
40+
},
41+
},
42+
want: nil,
43+
},
44+
{
45+
name: "No ignore paths when controlPlaneEndpoint is nil",
46+
infrastructureCluster: &unstructured.Unstructured{
47+
Object: map[string]interface{}{
48+
"spec": map[string]interface{}{
49+
"controlPlaneEndpoint": nil,
50+
},
51+
},
52+
},
53+
want: nil,
54+
},
55+
{
56+
name: "No ignore paths when controlPlaneEndpoint is an empty object",
57+
infrastructureCluster: &unstructured.Unstructured{
58+
Object: map[string]interface{}{
59+
"spec": map[string]interface{}{
60+
"controlPlaneEndpoint": map[string]interface{}{},
61+
},
62+
},
63+
},
64+
want: nil,
65+
},
66+
{
67+
name: "Don't ignore host when controlPlaneEndpoint.host is set",
68+
infrastructureCluster: &unstructured.Unstructured{
69+
Object: map[string]interface{}{
70+
"spec": map[string]interface{}{
71+
"controlPlaneEndpoint": map[string]interface{}{
72+
"host": "example.com",
73+
},
74+
},
75+
},
76+
},
77+
want: nil,
78+
},
79+
{
80+
name: "Ignore host when controlPlaneEndpoint.host is set to its zero value",
81+
infrastructureCluster: &unstructured.Unstructured{
82+
Object: map[string]interface{}{
83+
"spec": map[string]interface{}{
84+
"controlPlaneEndpoint": map[string]interface{}{
85+
"host": "",
86+
},
87+
},
88+
},
89+
},
90+
want: []Path{
91+
{"spec", "controlPlaneEndpoint", "host"},
92+
},
93+
},
94+
{
95+
name: "Don't ignore port when controlPlaneEndpoint.port is set",
96+
infrastructureCluster: &unstructured.Unstructured{
97+
Object: map[string]interface{}{
98+
"spec": map[string]interface{}{
99+
"controlPlaneEndpoint": map[string]interface{}{
100+
"port": int64(6443),
101+
},
102+
},
103+
},
104+
},
105+
want: nil,
106+
},
107+
{
108+
name: "Ignore port when controlPlaneEndpoint.port is set to its zero value",
109+
infrastructureCluster: &unstructured.Unstructured{
110+
Object: map[string]interface{}{
111+
"spec": map[string]interface{}{
112+
"controlPlaneEndpoint": map[string]interface{}{
113+
"port": int64(0),
114+
},
115+
},
116+
},
117+
},
118+
want: []Path{
119+
{"spec", "controlPlaneEndpoint", "port"},
120+
},
121+
},
122+
{
123+
name: "Ignore host and port when controlPlaneEndpoint host and port are set to their zero values",
124+
infrastructureCluster: &unstructured.Unstructured{
125+
Object: map[string]interface{}{
126+
"spec": map[string]interface{}{
127+
"controlPlaneEndpoint": map[string]interface{}{
128+
"host": "",
129+
"port": int64(0),
130+
},
131+
},
132+
},
133+
},
134+
want: []Path{
135+
{"spec", "controlPlaneEndpoint", "host"},
136+
{"spec", "controlPlaneEndpoint", "port"},
137+
},
138+
},
139+
{
140+
name: "Ignore host when controlPlaneEndpoint host is to its zero values, even if port is set",
141+
infrastructureCluster: &unstructured.Unstructured{
142+
Object: map[string]interface{}{
143+
"spec": map[string]interface{}{
144+
"controlPlaneEndpoint": map[string]interface{}{
145+
"host": "",
146+
"port": int64(6443),
147+
},
148+
},
149+
},
150+
},
151+
want: []Path{
152+
{"spec", "controlPlaneEndpoint", "host"},
153+
},
154+
},
155+
{
156+
name: "Ignore port when controlPlaneEndpoint port is to its zero values, even if host is set",
157+
infrastructureCluster: &unstructured.Unstructured{
158+
Object: map[string]interface{}{
159+
"spec": map[string]interface{}{
160+
"controlPlaneEndpoint": map[string]interface{}{
161+
"host": "example.com",
162+
"port": int64(0),
163+
},
164+
},
165+
},
166+
},
167+
want: []Path{
168+
{"spec", "controlPlaneEndpoint", "port"},
169+
},
170+
},
171+
}
172+
173+
for _, tt := range tests {
174+
t.Run(tt.name, func(t *testing.T) {
175+
g := NewWithT(t)
176+
177+
got, err := InfrastructureCluster().IgnorePaths(tt.infrastructureCluster)
178+
179+
if tt.expectErr {
180+
g.Expect(err).To(HaveOccurred())
181+
return
182+
}
183+
g.Expect(err).ToNot(HaveOccurred())
184+
g.Expect(got).To(Equal(tt.want))
185+
})
186+
}
32187
}

internal/controllers/topology/cluster/reconcile_state.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,17 @@ func (r *Reconciler) callAfterClusterUpgrade(ctx context.Context, s *scope.Scope
296296
// reconcileInfrastructureCluster reconciles the desired state of the InfrastructureCluster object.
297297
func (r *Reconciler) reconcileInfrastructureCluster(ctx context.Context, s *scope.Scope) error {
298298
ctx, _ = tlog.LoggerFrom(ctx).WithObject(s.Desired.InfrastructureCluster).Into(ctx)
299+
300+
ignorePaths, err := contract.InfrastructureCluster().IgnorePaths(s.Desired.InfrastructureCluster)
301+
if err != nil {
302+
return errors.Wrap(err, "failed to calculate ignore paths")
303+
}
304+
299305
return r.reconcileReferencedObject(ctx, reconcileReferencedObjectInput{
300306
cluster: s.Current.Cluster,
301307
current: s.Current.InfrastructureCluster,
302308
desired: s.Desired.InfrastructureCluster,
303-
ignorePaths: contract.InfrastructureCluster().IgnorePaths(),
309+
ignorePaths: ignorePaths,
304310
})
305311
}
306312

0 commit comments

Comments
 (0)