|
| 1 | +/* |
| 2 | +Copyright 2024 The Kubernetes Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package gce |
| 18 | + |
| 19 | +import ( |
| 20 | + "fmt" |
| 21 | + "strings" |
| 22 | + |
| 23 | + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" |
| 24 | + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" |
| 25 | + networkingv1 "k8s.io/api/networking/v1" |
| 26 | + "k8s.io/apimachinery/pkg/types" |
| 27 | + "k8s.io/apimachinery/pkg/util/validation/field" |
| 28 | + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" |
| 29 | +) |
| 30 | + |
| 31 | +// isSupportedGCEIngressClass verifies if the given ingress is supported by |
| 32 | +// GCE. |
| 33 | +// `kubernetes.io/ingress.class` annotation is still used by GCE to specify the |
| 34 | +// type of ingresses. |
| 35 | +// If the value of this annotation is one of the supported values(`gce` or |
| 36 | +// `gce-internal`), `ingressClassName` is ignored by GKE Ingress controller. |
| 37 | +// If the annotation is not set, the GKE Ingress controller will only process |
| 38 | +// the ingress if its ingressClassName is one of the supported values(`gce` or |
| 39 | +// `gce-internal`) or empty. |
| 40 | +func isSupportedGCEIngressClass(ingress networkingv1.Ingress) bool { |
| 41 | + legacyIngressClass := common.GetIngressClass(ingress) |
| 42 | + if legacyIngressClass != "" { |
| 43 | + return legacyIngressClass == gceIngressClass || legacyIngressClass == gceL7ILBIngressClass |
| 44 | + } |
| 45 | + if ingress.Spec.IngressClassName == nil { |
| 46 | + return true |
| 47 | + } |
| 48 | + ingressClass := *ingress.Spec.IngressClassName |
| 49 | + return ingressClass == gceIngressClass || ingressClass == gceL7ILBIngressClass |
| 50 | +} |
| 51 | + |
| 52 | +// toGceGatewayClass updates the Gateway to use GCE GatewayClass. |
| 53 | +func toGceGatewayClass(ingresses []networkingv1.Ingress, gatewayResources *i2gw.GatewayResources) field.ErrorList { |
| 54 | + var errs field.ErrorList |
| 55 | + |
| 56 | + // Since we already validated ingress resources when reading, there are |
| 57 | + // only two cases here: |
| 58 | + // 1. `kubernetes.io/ingress.class` exists in ingress anntation. In this |
| 59 | + // case, we use it to map to the corresponding gateway class. |
| 60 | + // 2. Annotation does not exist. In this case, the ingress is defaulted |
| 61 | + // to use the GCE external ingress implementation, and should be |
| 62 | + // mapped to `gke-l7-gxlb`. |
| 63 | + for _, ingress := range ingresses { |
| 64 | + gwKey := types.NamespacedName{Namespace: ingress.Namespace, Name: common.GetIngressClass(ingress)} |
| 65 | + existingGateway := gatewayResources.Gateways[gwKey] |
| 66 | + |
| 67 | + newGateway, err := setGCEGatewayClass(ingress, existingGateway) |
| 68 | + if err != nil { |
| 69 | + errs = append(errs, err) |
| 70 | + } |
| 71 | + gatewayResources.Gateways[gwKey] = newGateway |
| 72 | + } |
| 73 | + if len(errs) > 0 { |
| 74 | + return errs |
| 75 | + } |
| 76 | + return nil |
| 77 | +} |
| 78 | + |
| 79 | +// setGCEGatewayClass sets the Gateway to the corresponding GCE GatewayClass. |
| 80 | +func setGCEGatewayClass(ingress networkingv1.Ingress, gateway gatewayv1.Gateway) (gatewayv1.Gateway, *field.Error) { |
| 81 | + ingressClass := common.GetIngressClass(ingress) |
| 82 | + |
| 83 | + ingressName := fmt.Sprintf("%s/%s", ingress.Namespace, ingress.Name) |
| 84 | + // Get GCE GatewayClass from from GCE Ingress class. |
| 85 | + newGatewayClass, err := ingClassToGwyClassGCE(ingressClass) |
| 86 | + if err != nil { |
| 87 | + return gateway, field.NotSupported(field.NewPath(ingressName).Child("ObjectMeta", "Annotations", "kubernetes.io/ingress.class"), ingressClass, []string{gceIngressClass, gceL7ILBIngressClass}) |
| 88 | + } |
| 89 | + gateway.Spec.GatewayClassName = gatewayv1.ObjectName(newGatewayClass) |
| 90 | + return gateway, nil |
| 91 | +} |
| 92 | + |
| 93 | +// ingClassToGwyClassGCE returns the corresponding GCE Gateway Class based on the |
| 94 | +// given GCE ingress class. |
| 95 | +func ingClassToGwyClassGCE(ingressClass string) (string, error) { |
| 96 | + switch ingressClass { |
| 97 | + case gceIngressClass: |
| 98 | + return gceL7GlobalExternalManagedGatewayClass, nil |
| 99 | + case gceL7ILBIngressClass: |
| 100 | + return gceL7RegionalInternalGatewayClass, nil |
| 101 | + case "": |
| 102 | + return gceL7GlobalExternalManagedGatewayClass, nil |
| 103 | + default: |
| 104 | + return "", fmt.Errorf("Given GCE Ingress Class not supported") |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +// checkImplementationSpecificPath checks if this ingress contains a |
| 109 | +// ImplementationSpecific path with * and throws an error if it does. |
| 110 | +func checkImplementationSpecificPath(ingress *networkingv1.Ingress) *field.Error { |
| 111 | + for _, rule := range ingress.Spec.Rules { |
| 112 | + for _, path := range rule.HTTP.Paths { |
| 113 | + if path.PathType != nil && *path.PathType == networkingv1.PathTypeImplementationSpecific && strings.HasSuffix(path.Path, "/*") { |
| 114 | + ingressName := fmt.Sprintf("%s/%s", ingress.Namespace, ingress.Name) |
| 115 | + detail := "ImplementationSpecific Path with * is not supported for GCE ingress2gateway conversion" |
| 116 | + return field.Invalid(field.NewPath(ingressName).Child("Spec", "Rules", "HTTP", "Paths", "Path"), path.Path, detail) |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | + return nil |
| 121 | +} |
0 commit comments