Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@
service FQDNs (e.g., `service.namespace.svc.cluster.local`) instead of
individual pod endpoint IPs.
[#2607](https://github.com/Kong/kong-operator/pull/2607)
- Gateway: support per-Gateway infrastructure configuration
[GEP-1867](https://gateway-api.sigs.k8s.io/geps/gep-1867/) via
`GatewayConfiguration` CRD.
[#2653](https://github.com/Kong/kong-operator/pull/2653)

### Changed

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# GEP-1867: Per-Gateway Infrastructure
# https://gateway-api.sigs.k8s.io/geps/gep-1867/
# This sample demonstrates how to configure a hybrid Kong Gateway with
# per-Gateway infrastructure using GatewayConfiguration resources.
# The GatewayConfiguration resource named "hybrid-kong" defines
# the control plane connection details for the data plane.
# The GatewayConfiguration resource named "gateway-infrastructure-kong"
# defines the infrastructure details for the Gateway
# setting the source to "Mirror" and specifying the Konnect Control Plane ID.
---
kind: KonnectAPIAuthConfiguration
apiVersion: konnect.konghq.com/v1alpha1
metadata:
name: konnect-api-auth
namespace: default
spec:
type: token
token: kpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
serverURL: eu.api.konghq.com
---
kind: GatewayConfiguration
apiVersion: gateway-operator.konghq.com/v2beta1
metadata:
name: hybrid-kong
namespace: default
spec:
konnect:
authRef:
name: konnect-api-auth
dataPlaneOptions:
deployment:
podTemplateSpec:
spec:
containers:
- name: proxy
# renovate: datasource=docker versioning=docker
image: kong/kong-gateway:3.12
readinessProbe:
initialDelaySeconds: 1
periodSeconds: 1
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: hybrid-kong
spec:
controllerName: konghq.com/gateway-operator
parametersRef:
group: gateway-operator.konghq.com
kind: GatewayConfiguration
name: hybrid-kong
namespace: default
---
kind: GatewayConfiguration
apiVersion: gateway-operator.konghq.com/v2beta1
metadata:
name: gateway-infrastructure-kong
namespace: default
spec:
konnect:
source: Mirror
mirror:
konnect:
id: 03b496a0-a38a-4370-8e50-9a9e71cef6df
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: hybrid-kong
namespace: default
spec:
gatewayClassName: hybrid-kong
infrastructure:
parametersRef:
group: gateway-operator.konghq.com
kind: GatewayConfiguration
name: gateway-infrastructure-kong
listeners:
- name: http
protocol: HTTP
port: 80
2 changes: 1 addition & 1 deletion controller/gateway/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
}

log.Trace(logger, "determining configuration")
gatewayConfig, err := r.getOrCreateGatewayConfiguration(ctx, gwc.GatewayClass)
gatewayConfig, err := r.getOrCreateGatewayConfiguration(ctx, gwc.GatewayClass, &gateway)
if err != nil {
return ctrl.Result{}, err
}
Expand Down
100 changes: 82 additions & 18 deletions controller/gateway/controller_reconciler_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

Expand All @@ -34,7 +35,6 @@ import (
"github.com/kong/kong-operator/controller/pkg/extensions"
"github.com/kong/kong-operator/controller/pkg/secrets"
"github.com/kong/kong-operator/controller/pkg/secrets/ref"
operatorerrors "github.com/kong/kong-operator/internal/errors"
gwtypes "github.com/kong/kong-operator/internal/types"
"github.com/kong/kong-operator/pkg/consts"
gatewayutils "github.com/kong/kong-operator/pkg/utils/gateway"
Expand Down Expand Up @@ -325,38 +325,99 @@ func gatewayAddressesFromService(svc corev1.Service) ([]gwtypes.GatewayStatusAdd
return addresses, nil
}

// mergeGatewayConfigurations merges two GatewayConfiguration objects.
func mergeGatewayConfigurations(gatewayConfig *GatewayConfiguration, localGatewayConfig *GatewayConfiguration) (*GatewayConfiguration, error) {
// If localGatewayConfig is nil, return the original gatewayConfig
if localGatewayConfig == nil {
return gatewayConfig, nil
}

// Marshal both configurations to JSON
baseBytes, err := json.Marshal(gatewayConfig)
if err != nil {
return nil, fmt.Errorf("failed to marshal JSON for gatewayConfig %s: %w", gatewayConfig.Name, err)
}

patchBytes, err := json.Marshal(localGatewayConfig)
if err != nil {
return nil, fmt.Errorf("failed to marshal JSON for localGatewayConfig %s: %w", localGatewayConfig.Name, err)
}

// Perform the strategic merge patch
jsonResultBytes, err := strategicpatch.StrategicMergePatch(baseBytes, patchBytes, &GatewayConfiguration{})
if err != nil {
return nil, fmt.Errorf("failed to generate merge patch for %s: %w", gatewayConfig.Name, err)
}

// Unmarshal the result back to a GatewayConfiguration
mergedConfig := gatewayConfig.DeepCopy()
if err := json.Unmarshal(jsonResultBytes, mergedConfig); err != nil {
return nil, fmt.Errorf("failed to unmarshal merged %s: %w", gatewayConfig.Name, err)
}

return mergedConfig, nil
}

func (r *Reconciler) getOrCreateGatewayConfiguration(
ctx context.Context,
gatewayClass *gatewayv1.GatewayClass,
gateway *gatewayv1.Gateway,
) (*GatewayConfiguration, error) {
gatewayConfig, err := r.getGatewayConfigForGatewayClass(ctx, gatewayClass)
gatewayConfig, err := r.getGatewayConfigForParametersRef(ctx, gatewayClass.Spec.ParametersRef)
if err != nil {
if errors.Is(err, operatorerrors.ErrObjectMissingParametersRef) {
return new(GatewayConfiguration), nil
return nil, fmt.Errorf("GatewayClass (%s): %w", gatewayClass.Name, err)
}

// Create a new variable of type *gatewayv1.ParametersReference
localParametersRef := &gatewayv1.ParametersReference{}

// Copy fields from gateway.Spec.Infrastructure.ParametersRef
if gateway.Spec.Infrastructure != nil &&
gateway.Spec.Infrastructure.ParametersRef != nil {
localParametersRef.Group = gateway.Spec.Infrastructure.ParametersRef.Group
localParametersRef.Kind = gateway.Spec.Infrastructure.ParametersRef.Kind
localParametersRef.Name = gateway.Spec.Infrastructure.ParametersRef.Name

// Namespace will be set as the gateway's namespace
namespace := gatewayv1.Namespace(gateway.Namespace)
localParametersRef.Namespace = &namespace

localGatewayConfig, err := r.getGatewayConfigForParametersRef(ctx, localParametersRef)
if err != nil {
return nil, fmt.Errorf("Gateway (%s): spec.instrastructure %w", gateway.Name, err)
}
return nil, err

// Merge localGatewayConfig into gatewayConfig
mergedConfig, err := mergeGatewayConfigurations(gatewayConfig, localGatewayConfig)
if err != nil {
return nil, fmt.Errorf("Gateway (%s): spec.instrastructure %w", gateway.Name, err)
}

return mergedConfig, nil
}

return gatewayConfig, nil
}

func (r *Reconciler) getGatewayConfigForGatewayClass(
func (r *Reconciler) getGatewayConfigForParametersRef(
ctx context.Context,
gatewayClass *gatewayv1.GatewayClass,
parametersRef *gatewayv1.ParametersReference,
) (*GatewayConfiguration, error) {
if gatewayClass.Spec.ParametersRef == nil {
return nil, fmt.Errorf("%w, gatewayClass = %s", operatorerrors.ErrObjectMissingParametersRef, gatewayClass.Name)
// No parametersRef means using default configuration.
// Return an empty GatewayConfiguration object.
if parametersRef == nil {
return new(GatewayConfiguration), nil
}

if string(gatewayClass.Spec.ParametersRef.Group) != operatorv1beta1.SchemeGroupVersion.Group ||
string(gatewayClass.Spec.ParametersRef.Kind) != "GatewayConfiguration" {
if string(parametersRef.Group) != operatorv1beta1.SchemeGroupVersion.Group ||
string(parametersRef.Kind) != "GatewayConfiguration" {
return nil, &k8serrors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusBadRequest,
Reason: metav1.StatusReasonInvalid,
Details: &metav1.StatusDetails{
Kind: string(gatewayClass.Spec.ParametersRef.Kind),
Kind: string(parametersRef.Kind),
Causes: []metav1.StatusCause{{
Type: metav1.CauseTypeFieldValueNotSupported,
Message: fmt.Sprintf("controller only supports %s %s resources for GatewayClass parametersRef",
Expand All @@ -367,17 +428,20 @@ func (r *Reconciler) getGatewayConfigForGatewayClass(
}
}

if gatewayClass.Spec.ParametersRef.Namespace == nil ||
*gatewayClass.Spec.ParametersRef.Namespace == "" ||
gatewayClass.Spec.ParametersRef.Name == "" {
return nil, fmt.Errorf("GatewayClass %s has invalid ParametersRef: both namespace and name must be provided", gatewayClass.Name)
if parametersRef.Namespace == nil ||
*parametersRef.Namespace == "" {
return nil, fmt.Errorf("ParametersRef: namespace must be provided")
}

if parametersRef.Name == "" {
return nil, fmt.Errorf("ParametersRef: name must be provided")
}

var (
gatewayConfig GatewayConfiguration
nn = types.NamespacedName{
Namespace: string(*gatewayClass.Spec.ParametersRef.Namespace),
Name: gatewayClass.Spec.ParametersRef.Name,
Namespace: string(*parametersRef.Namespace),
Name: parametersRef.Name,
}
)

Expand Down
Loading
Loading