-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathops_controlplane.go
More file actions
370 lines (331 loc) · 12 KB
/
ops_controlplane.go
File metadata and controls
370 lines (331 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
package ops
import (
"context"
"fmt"
"sort"
sdkkonnectgo "github.com/Kong/sdk-konnect-go"
sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components"
sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations"
"github.com/sourcegraph/conc/iter"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
commonv1alpha1 "github.com/kong/kong-operator/v2/api/common/v1alpha1"
konnectv1alpha1 "github.com/kong/kong-operator/v2/api/konnect/v1alpha1"
konnectv1alpha2 "github.com/kong/kong-operator/v2/api/konnect/v1alpha2"
"github.com/kong/kong-operator/v2/controller/pkg/patch"
)
// ensureControlPlane ensures that the Konnect ControlPlane exists in Konnect. It is created
// if it does not exist and the source is Origin. If the source is Mirror, it checks
// if the ControlPlane exists in Konnect and returns an error if it does not.
func ensureControlPlane(
ctx context.Context,
sdk sdkkonnectgo.ControlPlanesSDK,
sdkGroups sdkkonnectgo.ControlPlaneGroupsSDK,
cl client.Client,
cp *konnectv1alpha2.KonnectGatewayControlPlane,
) error {
switch *cp.Spec.Source {
case commonv1alpha1.EntitySourceOrigin:
return createControlPlane(ctx, sdk, sdkGroups, cl, cp)
case commonv1alpha1.EntitySourceMirror:
resp, err := GetControlPlaneByID(
ctx,
sdk,
// not nilness is ensured by CEL rules
string(cp.Spec.Mirror.Konnect.ID),
)
if err != nil {
return err
}
old := cp.DeepCopy()
cp.SetKonnectID(string(cp.Spec.Mirror.Konnect.ID))
if fillKonnectGatewayControlPlaneStatusFromResponse(cp, resp.Config) {
_, err = patch.ApplyStatusPatchIfNotEmpty(ctx, cl, ctrllog.FromContext(ctx), cp, old)
return err
}
return nil
default:
// This should never happen, as the source type is validated by CEL rules.
return fmt.Errorf("unsupported source type: %s", *cp.Spec.Source)
}
}
// createControlPlane creates the ControlPlane as specified in provided ControlPlane's
// spec. Besides creating the ControlPlane, it also creates the group membership if the
// ControlPlane is a group. If the group membership creation fails, KonnectEntityCreatedButRelationsFailedError
// is returned so it can be handled properly downstream.
func createControlPlane(
ctx context.Context,
sdk sdkkonnectgo.ControlPlanesSDK,
sdkGroups sdkkonnectgo.ControlPlaneGroupsSDK,
cl client.Client,
cp *konnectv1alpha2.KonnectGatewayControlPlane,
) error {
req := cp.Spec.CreateControlPlaneRequest
req.Labels = WithKubernetesMetadataLabels(cp, req.Labels)
resp, err := sdk.CreateControlPlane(ctx, *req)
if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, cp); errWrap != nil {
return errWrap
}
if resp == nil || resp.ControlPlane == nil || resp.ControlPlane.ID == "" {
return fmt.Errorf("failed creating %s: %w", cp.GetTypeName(), ErrNilResponse)
}
// At this point, the ControlPlane has been created in Konnect.
id := resp.ControlPlane.ID
old := cp.DeepCopy()
cp.SetKonnectID(id)
if fillKonnectGatewayControlPlaneStatusFromResponse(cp, resp.ControlPlane.Config) {
_, err = patch.ApplyStatusPatchIfNotEmpty(ctx, cl, ctrllog.FromContext(ctx), cp, old)
if err != nil {
return err
}
}
if err := setGroupMembers(ctx, cl, cp, id, sdkGroups); err != nil {
// If we failed to set group membership, we should return a specific error with a reason
// so the downstream can handle it properly.
return KonnectEntityCreatedButRelationsFailedError{
KonnectID: id,
Err: err,
Reason: konnectv1alpha1.KonnectGatewayControlPlaneProgrammedReasonFailedToSetControlPlaneGroupMembers,
}
}
return nil
}
// deleteControlPlane deletes a Konnect ControlPlane.
// It is assumed that the Konnect ControlPlane has a Konnect ID.
func deleteControlPlane(
ctx context.Context,
sdk sdkkonnectgo.ControlPlanesSDK,
cp *konnectv1alpha2.KonnectGatewayControlPlane,
) error {
// if the source type is Mirror, don't touch the Konnect entity.
if isMirrorEntity(cp) {
return nil
}
id := cp.GetKonnectStatus().GetKonnectID()
_, err := sdk.DeleteControlPlane(ctx, id)
if errWrap := wrapErrIfKonnectOpFailed(err, DeleteOp, cp); errWrap != nil {
return handleDeleteError(ctx, err, cp)
}
return nil
}
// updateControlPlane updates a Konnect ControlPlane.
// It is assumed that the Konnect ControlPlane has a Konnect ID.
// Besides updating the ControlPlane, it also updates the group membership if the ControlPlane is a group.
// If the group membership update fails, KonnectEntityCreatedButRelationsFailedError is returned so it can
// be handled properly downstream.
func updateControlPlane(
ctx context.Context,
sdk sdkkonnectgo.ControlPlanesSDK,
sdkGroups sdkkonnectgo.ControlPlaneGroupsSDK,
cl client.Client,
cp *konnectv1alpha2.KonnectGatewayControlPlane,
) error {
// if the source type is Mirror, don't touch the Konnect entity.
if isMirrorEntity(cp) {
return nil
}
id := cp.GetKonnectStatus().GetKonnectID()
req := sdkkonnectcomp.UpdateControlPlaneRequest{
Name: new(cp.GetKonnectName()),
Description: cp.GetKonnectDescription(),
AuthType: (*sdkkonnectcomp.UpdateControlPlaneRequestAuthType)(cp.GetKonnectAuthType()),
ProxyUrls: cp.GetKonnectProxyURLs(),
Labels: WithKubernetesMetadataLabels(cp, cp.GetKonnectLabels()),
}
resp, err := sdk.UpdateControlPlane(ctx, id, req)
if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, cp); errWrap != nil {
return handleUpdateError(ctx, err, cp, func(ctx context.Context) error {
return createControlPlane(ctx, sdk, sdkGroups, cl, cp)
})
}
if resp == nil || resp.ControlPlane == nil {
return fmt.Errorf("failed updating ControlPlane: %w", ErrNilResponse)
}
old := cp.DeepCopy()
if fillKonnectGatewayControlPlaneStatusFromResponse(cp, resp.ControlPlane.Config) {
_, err = patch.ApplyStatusPatchIfNotEmpty(ctx, cl, ctrllog.FromContext(ctx), cp, old)
if err != nil {
return err
}
}
id = resp.ControlPlane.ID
if err := setGroupMembers(ctx, cl, cp, id, sdkGroups); err != nil {
// If we failed to set group membership, we should return a specific error with a reason
// so the downstream can handle it properly.
return KonnectEntityCreatedButRelationsFailedError{
KonnectID: id,
Err: err,
Reason: konnectv1alpha1.KonnectGatewayControlPlaneProgrammedReasonFailedToSetControlPlaneGroupMembers,
}
}
return nil
}
func setGroupMembers(
ctx context.Context,
cl client.Client,
cp *konnectv1alpha2.KonnectGatewayControlPlane,
id string,
sdkGroups sdkkonnectgo.ControlPlaneGroupsSDK,
) error {
// if the source type is Mirror, don't touch the Konnect entity.
if isMirrorEntity(cp) {
return nil
}
if cp.GetKonnectClusterType() == nil ||
*cp.GetKonnectClusterType() != sdkkonnectcomp.CreateControlPlaneRequestClusterTypeClusterTypeControlPlaneGroup {
return nil
}
members, err := iter.MapErr(cp.Spec.Members,
func(member *corev1.LocalObjectReference) (sdkkonnectcomp.Members, error) {
var (
memberCP konnectv1alpha2.KonnectGatewayControlPlane
nn = client.ObjectKey{
Namespace: cp.Namespace,
Name: member.Name,
}
)
if err := cl.Get(ctx, nn, &memberCP); err != nil {
return sdkkonnectcomp.Members{},
GetControlPlaneGroupMemberFailedError{
MemberName: memberCP.Name,
Err: err,
}
}
if memberCP.GetKonnectID() == "" {
return sdkkonnectcomp.Members{},
ControlPlaneGroupMemberNoKonnectIDError{
GroupName: cp.Name,
MemberName: memberCP.Name,
}
}
return sdkkonnectcomp.Members{
ID: memberCP.GetKonnectID(),
}, nil
})
if err != nil {
SetControlPlaneGroupMembersReferenceResolvedConditionFalse(
cp,
ControlPlaneGroupMembersReferenceResolvedReasonPartialNotResolved,
err.Error(),
)
return fmt.Errorf("failed to set group members, some members couldn't be found: %w", err)
}
sort.Sort(membersByID(members))
gm := sdkkonnectcomp.GroupMembership{
Members: members,
}
_, err = sdkGroups.PutControlPlanesIDGroupMemberships(ctx, id, &gm)
if err != nil {
SetControlPlaneGroupMembersReferenceResolvedConditionFalse(
cp,
ControlPlaneGroupMembersReferenceResolvedReasonFailedToSet,
err.Error(),
)
return fmt.Errorf("failed to set members on control plane group %s: %w",
client.ObjectKeyFromObject(cp), err,
)
}
SetControlPlaneGroupMembersReferenceResolvedCondition(
cp,
)
return nil
}
type membersByID []sdkkonnectcomp.Members
func (m membersByID) Len() int { return len(m) }
func (m membersByID) Less(i, j int) bool { return m[i].ID < m[j].ID }
func (m membersByID) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
// getControlPlaneForUID returns the Konnect ID of the Konnect ControlPlane
// that matches the UID of the provided KonnectGatewayControlPlane.
func getControlPlaneForUID(
ctx context.Context,
sdk sdkkonnectgo.ControlPlanesSDK,
sdkGroups sdkkonnectgo.ControlPlaneGroupsSDK,
cl client.Client,
cp *konnectv1alpha2.KonnectGatewayControlPlane,
) (string, error) {
reqList := sdkkonnectops.ListControlPlanesRequest{
// NOTE: only filter on object's UID.
// Other fields like name might have changed in the meantime but that's OK.
// Those will be enforced via subsequent updates.
FilterLabels: new(UIDLabelForObject(cp)),
}
resp, err := sdk.ListControlPlanes(ctx, reqList)
if err != nil {
return "", fmt.Errorf("failed listing %s: %w", cp.GetTypeName(), err)
}
if resp == nil || resp.ListControlPlanesResponse == nil {
return "", fmt.Errorf("failed listing %s: %w", cp.GetTypeName(), ErrNilResponse)
}
entry, id, err := getMatchingEntryFromListResponseData(sliceToEntityWithIDSlice(resp.ListControlPlanesResponse.Data), cp)
if err != nil {
return "", err
}
old := cp.DeepCopy()
if fillKonnectGatewayControlPlaneStatusFromResponse(cp, entry.Config) {
_, err = patch.ApplyStatusPatchIfNotEmpty(ctx, cl, ctrllog.FromContext(ctx), cp, old)
if err != nil {
return id, err
}
}
if err := setGroupMembers(ctx, cl, cp, id, sdkGroups); err != nil {
// If we failed to set group membership, we should return a specific error with a reason
// so the downstream can handle it properly.
return id, KonnectEntityCreatedButRelationsFailedError{
KonnectID: id,
Err: err,
Reason: konnectv1alpha1.KonnectGatewayControlPlaneProgrammedReasonFailedToSetControlPlaneGroupMembers,
}
}
return id, nil
}
// GetControlPlaneByID returns the Konnect ControlPlane that matches the provided ID.
func GetControlPlaneByID(
ctx context.Context,
sdk sdkkonnectgo.ControlPlanesSDK,
id string,
) (*sdkkonnectcomp.ControlPlane, error) {
reqList := sdkkonnectops.ListControlPlanesRequest{
Filter: &sdkkonnectcomp.ControlPlaneFilterParameters{
ID: &sdkkonnectcomp.ID{
Eq: new(id),
},
},
}
resp, err := sdk.ListControlPlanes(ctx, reqList)
if err != nil || resp == nil || resp.ListControlPlanesResponse == nil {
return nil, fmt.Errorf("failed listing for controlPlane with id %s: %w", id, err)
}
if len(resp.ListControlPlanesResponse.Data) == 0 {
return nil, fmt.Errorf("failed listing controlPlanes by id: %w", EntityWithMatchingIDNotFoundError{ID: id})
}
// This should never happen, as ID is unique.
if len(resp.ListControlPlanesResponse.Data) > 1 {
return nil, fmt.Errorf("failed listing controlPlanes by id: %w", MultipleEntitiesWithMatchingIDFoundError{ID: id})
}
return &resp.ListControlPlanesResponse.Data[0], nil
}
func fillKonnectGatewayControlPlaneStatusFromResponse(
cp *konnectv1alpha2.KonnectGatewayControlPlane,
respConfig sdkkonnectcomp.ControlPlaneConfig,
) bool {
if cp == nil {
return false
}
var updated bool
if respConfig.ClusterType != "" {
cp.Status.ClusterType = respConfig.ClusterType
updated = true
}
if respConfig.ControlPlaneEndpoint != "" &&
(cp.Status.Endpoints == nil || respConfig.ControlPlaneEndpoint != cp.Status.Endpoints.ControlPlaneEndpoint) ||
respConfig.TelemetryEndpoint != "" &&
(cp.Status.Endpoints == nil || respConfig.TelemetryEndpoint != cp.Status.Endpoints.TelemetryEndpoint) {
cp.Status.Endpoints = &konnectv1alpha2.KonnectEndpoints{
ControlPlaneEndpoint: respConfig.ControlPlaneEndpoint,
TelemetryEndpoint: respConfig.TelemetryEndpoint,
}
updated = true
}
return updated
}