Skip to content

Commit 7afc205

Browse files
committed
Add resource reader for gce
1 parent 9e9f70f commit 7afc205

File tree

5 files changed

+385
-8
lines changed

5 files changed

+385
-8
lines changed

pkg/i2gw/providers/gce/gce.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"k8s.io/apimachinery/pkg/util/validation/field"
2525
)
2626

27-
// The ProviderName returned to the provider's registry.
2827
const ProviderName = "gce"
2928

3029
func init() {
@@ -57,8 +56,8 @@ func (p *Provider) ReadResourcesFromCluster(ctx context.Context) error {
5756
return nil
5857
}
5958

60-
func (p *Provider) ReadResourcesFromFile(ctx context.Context, filename string) error {
61-
storage, err := p.reader.readResourcesFromFile(ctx, filename)
59+
func (p *Provider) ReadResourcesFromFile(_ context.Context, filename string) error {
60+
storage, err := p.reader.readResourcesFromFile(filename)
6261
if err != nil {
6362
return fmt.Errorf("failed to read resources from file: %w", err)
6463
}

pkg/i2gw/providers/gce/resource_reader.go

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ limitations under the License.
1717
package gce
1818

1919
import (
20+
"bytes"
2021
"context"
22+
"fmt"
23+
"os"
2124

2225
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
26+
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
27+
networkingv1 "k8s.io/api/networking/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/types"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
2331
)
2432

2533
// converter implements the i2gw.CustomResourceReader interface.
@@ -35,11 +43,78 @@ func newResourceReader(conf *i2gw.ProviderConf) reader {
3543
}
3644

3745
func (r *reader) readResourcesFromCluster(ctx context.Context) (*storage, error) {
38-
// read example-gateway related resources from the cluster.
39-
return nil, nil
46+
storage := newResourcesStorage()
47+
48+
ingresses, err := r.readGCEIngressesFromCluster(ctx, r.conf.Client)
49+
if err != nil {
50+
return nil, err
51+
}
52+
storage.Ingresses = ingresses
53+
54+
return storage, nil
55+
}
56+
57+
func (r *reader) readResourcesFromFile(filename string) (*storage, error) {
58+
storage := newResourcesStorage()
59+
60+
ingresses, err := r.readGCEIngressesFromFile(filename, r.conf.Namespace)
61+
if err != nil {
62+
return nil, err
63+
}
64+
storage.Ingresses = ingresses
65+
66+
return storage, nil
67+
}
68+
69+
// readGCEIngressesFromCluster lists ingresses from cluster, validates ingress
70+
// class annotation used by GCE, and return the list of supported ingresses.
71+
func (r *reader) readGCEIngressesFromCluster(ctx context.Context, client client.Client) (map[types.NamespacedName]*networkingv1.Ingress, error) {
72+
var ingressList networkingv1.IngressList
73+
err := client.List(ctx, &ingressList)
74+
if err != nil {
75+
return nil, fmt.Errorf("failed to get ingresses from the cluster: %w", err)
76+
}
77+
78+
ingresses := map[types.NamespacedName]*networkingv1.Ingress{}
79+
for i, ingress := range ingressList.Items {
80+
if !isSupportedGCEIngressClass(ingress) {
81+
continue
82+
}
83+
ingresses[types.NamespacedName{Namespace: ingress.Namespace, Name: ingress.Name}] = &ingressList.Items[i]
84+
}
85+
86+
return ingresses, nil
4087
}
4188

42-
func (r *reader) readResourcesFromFile(ctx context.Context, filename string) (*storage, error) {
43-
// read example-gateway related resources from the file.
44-
return nil, nil
89+
// readGCEIngressesFromCluster reads ingress configuration from the given file,
90+
// validates ingress class annotation used by GCE, and return the list of
91+
// supported ingresses.
92+
func (r *reader) readGCEIngressesFromFile(filename string, namespace string) (map[types.NamespacedName]*networkingv1.Ingress, error) {
93+
stream, err := os.ReadFile(filename)
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to read file %v: %w", filename, err)
96+
}
97+
98+
unstructuredObjects, err := common.ExtractObjectsFromReader(bytes.NewReader(stream), namespace)
99+
if err != nil {
100+
return nil, fmt.Errorf("failed to extract objects: %w", err)
101+
}
102+
103+
ingresses := map[types.NamespacedName]*networkingv1.Ingress{}
104+
for _, f := range unstructuredObjects {
105+
if !f.GroupVersionKind().Empty() && f.GroupVersionKind().Kind == "Ingress" {
106+
var ingress networkingv1.Ingress
107+
err = runtime.DefaultUnstructuredConverter.
108+
FromUnstructured(f.UnstructuredContent(), &ingress)
109+
if err != nil {
110+
return nil, err
111+
}
112+
if !isSupportedGCEIngressClass(ingress) {
113+
continue
114+
}
115+
ingresses[types.NamespacedName{Namespace: ingress.Namespace, Name: ingress.Name}] = &ingress
116+
}
117+
118+
}
119+
return ingresses, nil
45120
}

pkg/i2gw/providers/gce/types.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
const (
20+
gceIngressClass = "gce"
21+
gceL7ILBIngressClass = "gce-internal"
22+
23+
gceL7GlobalExternalManagedGatewayClass = "gke-l7-global-external-managed"
24+
gceL7RegionalInternalGatewayClass = "gke-l7-rilb"
25+
)

pkg/i2gw/providers/gce/utils.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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

Comments
 (0)