Skip to content

Commit fbd6f1a

Browse files
feat(gateway): add Gateway spec.infrastructure.parametersRef support (#2653)
1 parent 2f6a656 commit fbd6f1a

File tree

6 files changed

+814
-27
lines changed

6 files changed

+814
-27
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@
131131
service FQDNs (e.g., `service.namespace.svc.cluster.local`) instead of
132132
individual pod endpoint IPs.
133133
[#2607](https://github.com/Kong/kong-operator/pull/2607)
134+
- Gateway: support per-Gateway infrastructure configuration
135+
[GEP-1867](https://gateway-api.sigs.k8s.io/geps/gep-1867/) via
136+
`GatewayConfiguration` CRD.
137+
[#2653](https://github.com/Kong/kong-operator/pull/2653)
134138

135139
### Changed
136140

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# GEP-1867: Per-Gateway Infrastructure
2+
# https://gateway-api.sigs.k8s.io/geps/gep-1867/
3+
# This sample demonstrates how to configure a hybrid Kong Gateway with
4+
# per-Gateway infrastructure using GatewayConfiguration resources.
5+
# The GatewayConfiguration resource named "common-infra-configuration-kong" defines
6+
# the control plane connection details for the data plane.
7+
# The GatewayConfiguration resource named "custom-infra-configuration-kong"
8+
# defines the infrastructure details for the Gateway with custom settings by
9+
# setting the source to "Mirror" and specifying the Konnect Control Plane ID.
10+
---
11+
kind: KonnectAPIAuthConfiguration
12+
apiVersion: konnect.konghq.com/v1alpha1
13+
metadata:
14+
name: konnect-api-auth
15+
namespace: default
16+
spec:
17+
type: token
18+
token: kpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
19+
serverURL: eu.api.konghq.com
20+
---
21+
kind: GatewayConfiguration
22+
apiVersion: gateway-operator.konghq.com/v2beta1
23+
metadata:
24+
name: common-infra-configuration-kong
25+
namespace: default
26+
spec:
27+
konnect:
28+
authRef:
29+
name: konnect-api-auth
30+
dataPlaneOptions:
31+
deployment:
32+
podTemplateSpec:
33+
spec:
34+
containers:
35+
- name: proxy
36+
# renovate: datasource=docker versioning=docker
37+
image: kong/kong-gateway:3.12
38+
readinessProbe:
39+
initialDelaySeconds: 1
40+
periodSeconds: 1
41+
---
42+
kind: GatewayClass
43+
apiVersion: gateway.networking.k8s.io/v1
44+
metadata:
45+
name: hybrid-kong
46+
spec:
47+
controllerName: konghq.com/gateway-operator
48+
parametersRef:
49+
group: gateway-operator.konghq.com
50+
kind: GatewayConfiguration
51+
name: common-infra-configuration-kong
52+
namespace: default
53+
---
54+
kind: Gateway
55+
apiVersion: gateway.networking.k8s.io/v1
56+
metadata:
57+
name: common-hybrid-kong
58+
namespace: default
59+
spec:
60+
gatewayClassName: hybrid-kong
61+
listeners:
62+
- name: http
63+
protocol: HTTP
64+
port: 80
65+
---
66+
kind: GatewayConfiguration
67+
apiVersion: gateway-operator.konghq.com/v2beta1
68+
metadata:
69+
name: custom-infra-configuration-kong
70+
namespace: default
71+
spec:
72+
konnect:
73+
source: Mirror
74+
mirror:
75+
konnect:
76+
id: 03b496a0-a38a-4370-8e50-9a9e71cef6df
77+
---
78+
kind: Gateway
79+
apiVersion: gateway.networking.k8s.io/v1
80+
metadata:
81+
name: custom-hybrid-kong
82+
namespace: default
83+
spec:
84+
gatewayClassName: hybrid-kong
85+
infrastructure:
86+
parametersRef:
87+
group: gateway-operator.konghq.com
88+
kind: GatewayConfiguration
89+
name: custom-infra-configuration-kong
90+
listeners:
91+
- name: http
92+
protocol: HTTP
93+
port: 80

controller/gateway/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
231231
}
232232

233233
log.Trace(logger, "determining configuration")
234-
gatewayConfig, err := r.getOrCreateGatewayConfiguration(ctx, gwc.GatewayClass)
234+
gatewayConfig, err := r.getOrCreateGatewayConfiguration(ctx, gwc.GatewayClass, &gateway)
235235
if err != nil {
236236
return ctrl.Result{}, err
237237
}

controller/gateway/controller_reconciler_utils.go

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"k8s.io/apimachinery/pkg/labels"
2222
"k8s.io/apimachinery/pkg/types"
2323
"k8s.io/apimachinery/pkg/util/intstr"
24+
"k8s.io/apimachinery/pkg/util/strategicpatch"
2425
"sigs.k8s.io/controller-runtime/pkg/client"
2526
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
2627

@@ -34,7 +35,6 @@ import (
3435
"github.com/kong/kong-operator/controller/pkg/extensions"
3536
"github.com/kong/kong-operator/controller/pkg/secrets"
3637
"github.com/kong/kong-operator/controller/pkg/secrets/ref"
37-
operatorerrors "github.com/kong/kong-operator/internal/errors"
3838
gwtypes "github.com/kong/kong-operator/internal/types"
3939
"github.com/kong/kong-operator/pkg/consts"
4040
gatewayutils "github.com/kong/kong-operator/pkg/utils/gateway"
@@ -325,38 +325,99 @@ func gatewayAddressesFromService(svc corev1.Service) ([]gwtypes.GatewayStatusAdd
325325
return addresses, nil
326326
}
327327

328+
// mergeGatewayConfigurations merges two GatewayConfiguration objects.
329+
func mergeGatewayConfigurations(gatewayConfig *GatewayConfiguration, localGatewayConfig *GatewayConfiguration) (*GatewayConfiguration, error) {
330+
// If localGatewayConfig is nil, return the original gatewayConfig
331+
if localGatewayConfig == nil {
332+
return gatewayConfig, nil
333+
}
334+
335+
// Marshal both configurations to JSON
336+
baseBytes, err := json.Marshal(gatewayConfig)
337+
if err != nil {
338+
return nil, fmt.Errorf("failed to marshal JSON for gatewayConfig %s: %w", gatewayConfig.Name, err)
339+
}
340+
341+
patchBytes, err := json.Marshal(localGatewayConfig)
342+
if err != nil {
343+
return nil, fmt.Errorf("failed to marshal JSON for localGatewayConfig %s: %w", localGatewayConfig.Name, err)
344+
}
345+
346+
// Perform the strategic merge patch
347+
jsonResultBytes, err := strategicpatch.StrategicMergePatch(baseBytes, patchBytes, &GatewayConfiguration{})
348+
if err != nil {
349+
return nil, fmt.Errorf("failed to generate merge patch for %s: %w", gatewayConfig.Name, err)
350+
}
351+
352+
// Unmarshal the result back to a GatewayConfiguration
353+
mergedConfig := gatewayConfig.DeepCopy()
354+
if err := json.Unmarshal(jsonResultBytes, mergedConfig); err != nil {
355+
return nil, fmt.Errorf("failed to unmarshal merged %s: %w", gatewayConfig.Name, err)
356+
}
357+
358+
return mergedConfig, nil
359+
}
360+
328361
func (r *Reconciler) getOrCreateGatewayConfiguration(
329362
ctx context.Context,
330363
gatewayClass *gatewayv1.GatewayClass,
364+
gateway *gatewayv1.Gateway,
331365
) (*GatewayConfiguration, error) {
332-
gatewayConfig, err := r.getGatewayConfigForGatewayClass(ctx, gatewayClass)
366+
gatewayConfig, err := r.getGatewayConfigForParametersRef(ctx, gatewayClass.Spec.ParametersRef)
333367
if err != nil {
334-
if errors.Is(err, operatorerrors.ErrObjectMissingParametersRef) {
335-
return new(GatewayConfiguration), nil
368+
return nil, fmt.Errorf("GatewayClass (%s): %w", gatewayClass.Name, err)
369+
}
370+
371+
// Create a new variable of type *gatewayv1.ParametersReference
372+
localParametersRef := &gatewayv1.ParametersReference{}
373+
374+
// Copy fields from gateway.Spec.Infrastructure.ParametersRef
375+
if gateway.Spec.Infrastructure != nil &&
376+
gateway.Spec.Infrastructure.ParametersRef != nil {
377+
localParametersRef.Group = gateway.Spec.Infrastructure.ParametersRef.Group
378+
localParametersRef.Kind = gateway.Spec.Infrastructure.ParametersRef.Kind
379+
localParametersRef.Name = gateway.Spec.Infrastructure.ParametersRef.Name
380+
381+
// Namespace will be set as the gateway's namespace
382+
namespace := gatewayv1.Namespace(gateway.Namespace)
383+
localParametersRef.Namespace = &namespace
384+
385+
localGatewayConfig, err := r.getGatewayConfigForParametersRef(ctx, localParametersRef)
386+
if err != nil {
387+
return nil, fmt.Errorf("Gateway (%s): spec.instrastructure %w", gateway.Name, err)
336388
}
337-
return nil, err
389+
390+
// Merge localGatewayConfig into gatewayConfig
391+
mergedConfig, err := mergeGatewayConfigurations(gatewayConfig, localGatewayConfig)
392+
if err != nil {
393+
return nil, fmt.Errorf("Gateway (%s): spec.instrastructure %w", gateway.Name, err)
394+
}
395+
396+
return mergedConfig, nil
338397
}
339398

340399
return gatewayConfig, nil
341400
}
342401

343-
func (r *Reconciler) getGatewayConfigForGatewayClass(
402+
func (r *Reconciler) getGatewayConfigForParametersRef(
344403
ctx context.Context,
345-
gatewayClass *gatewayv1.GatewayClass,
404+
parametersRef *gatewayv1.ParametersReference,
346405
) (*GatewayConfiguration, error) {
347-
if gatewayClass.Spec.ParametersRef == nil {
348-
return nil, fmt.Errorf("%w, gatewayClass = %s", operatorerrors.ErrObjectMissingParametersRef, gatewayClass.Name)
406+
// No parametersRef means using default configuration.
407+
// Return an empty GatewayConfiguration object.
408+
if parametersRef == nil {
409+
return new(GatewayConfiguration), nil
349410
}
350411

351-
if string(gatewayClass.Spec.ParametersRef.Group) != operatorv1beta1.SchemeGroupVersion.Group ||
352-
string(gatewayClass.Spec.ParametersRef.Kind) != "GatewayConfiguration" {
412+
if string(parametersRef.Group) != operatorv1beta1.SchemeGroupVersion.Group ||
413+
string(parametersRef.Kind) != "GatewayConfiguration" {
353414
return nil, &k8serrors.StatusError{
354415
ErrStatus: metav1.Status{
355416
Status: metav1.StatusFailure,
356417
Code: http.StatusBadRequest,
357418
Reason: metav1.StatusReasonInvalid,
358419
Details: &metav1.StatusDetails{
359-
Kind: string(gatewayClass.Spec.ParametersRef.Kind),
420+
Kind: string(parametersRef.Kind),
360421
Causes: []metav1.StatusCause{{
361422
Type: metav1.CauseTypeFieldValueNotSupported,
362423
Message: fmt.Sprintf("controller only supports %s %s resources for GatewayClass parametersRef",
@@ -367,17 +428,20 @@ func (r *Reconciler) getGatewayConfigForGatewayClass(
367428
}
368429
}
369430

370-
if gatewayClass.Spec.ParametersRef.Namespace == nil ||
371-
*gatewayClass.Spec.ParametersRef.Namespace == "" ||
372-
gatewayClass.Spec.ParametersRef.Name == "" {
373-
return nil, fmt.Errorf("GatewayClass %s has invalid ParametersRef: both namespace and name must be provided", gatewayClass.Name)
431+
if parametersRef.Namespace == nil ||
432+
*parametersRef.Namespace == "" {
433+
return nil, fmt.Errorf("ParametersRef: namespace must be provided")
434+
}
435+
436+
if parametersRef.Name == "" {
437+
return nil, fmt.Errorf("ParametersRef: name must be provided")
374438
}
375439

376440
var (
377441
gatewayConfig GatewayConfiguration
378442
nn = types.NamespacedName{
379-
Namespace: string(*gatewayClass.Spec.ParametersRef.Namespace),
380-
Name: gatewayClass.Spec.ParametersRef.Name,
443+
Namespace: string(*parametersRef.Namespace),
444+
Name: parametersRef.Name,
381445
}
382446
)
383447

0 commit comments

Comments
 (0)