diff --git a/README.md b/README.md index aa1db5d7..9900a5d2 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ API. ## Supported providers * [apisix](pkg/i2gw/providers/apisix/README.md) +* [cilium](pkg./i2gw/providers/cilium/README.md) * [ingress-nginx](pkg/i2gw/providers/ingressnginx/README.md) * [istio](pkg/i2gw/providers/istio/README.md) * [gce](pkg/i2gw/providers/gce/README.md) diff --git a/cmd/print.go b/cmd/print.go index cf7d2f44..ff4ce90f 100644 --- a/cmd/print.go +++ b/cmd/print.go @@ -33,6 +33,7 @@ import ( // Call init function for the providers _ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/apisix" + _ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/cilium" _ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/gce" _ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/ingressnginx" _ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/istio" diff --git a/pkg/i2gw/intermediate/intermediate_representation.go b/pkg/i2gw/intermediate/intermediate_representation.go index 5fde45c5..9613c950 100644 --- a/pkg/i2gw/intermediate/intermediate_representation.go +++ b/pkg/i2gw/intermediate/intermediate_representation.go @@ -52,6 +52,7 @@ type GatewayContext struct { type ProviderSpecificGatewayIR struct { Apisix *ApisixGatewayIR + Cilium *CiliumGatewayIR Gce *GceGatewayIR IngressNginx *IngressNginxGatewayIR Istio *IstioGatewayIR @@ -71,6 +72,7 @@ type HTTPRouteContext struct { type ProviderSpecificHTTPRouteIR struct { Apisix *ApisixHTTPRouteIR + Cilium *CiliumHTTPRouteIR Gce *GceHTTPRouteIR IngressNginx *IngressNginxHTTPRouteIR Istio *IstioHTTPRouteIR @@ -82,6 +84,7 @@ type ProviderSpecificHTTPRouteIR struct { // extension features on Service. type ProviderSpecificServiceIR struct { Apisix *ApisixServiceIR + Cilium *CiliumServiceIR Gce *GceServiceIR IngressNginx *IngressNginxServiceIR Istio *IstioServiceIR diff --git a/pkg/i2gw/intermediate/provider_cilium.go b/pkg/i2gw/intermediate/provider_cilium.go new file mode 100644 index 00000000..5d6e8890 --- /dev/null +++ b/pkg/i2gw/intermediate/provider_cilium.go @@ -0,0 +1,21 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package intermediate + +type CiliumGatewayIR struct{} +type CiliumHTTPRouteIR struct{} +type CiliumServiceIR struct{} diff --git a/pkg/i2gw/providers/cilium/README.md b/pkg/i2gw/providers/cilium/README.md new file mode 100644 index 00000000..080bc763 --- /dev/null +++ b/pkg/i2gw/providers/cilium/README.md @@ -0,0 +1,8 @@ +# Cilium Provider + +The project supports translating [Cilium](https://github.com/cilium/cilium) specific annotations. + +## Supported Annotations + +- `ingress.cilium.io/force-https:`: This annotation redirects HTTP requests to HTTPS with a `301` status code. + diff --git a/pkg/i2gw/providers/cilium/annotations.go b/pkg/i2gw/providers/cilium/annotations.go new file mode 100644 index 00000000..fadfca65 --- /dev/null +++ b/pkg/i2gw/providers/cilium/annotations.go @@ -0,0 +1,27 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import "fmt" + +const ( + annotationPrefix = "ingress.cilium.io" +) + +func ciliumAnnotation(suffix string) string { + return fmt.Sprintf("%s/%s", annotationPrefix, suffix) +} diff --git a/pkg/i2gw/providers/cilium/cilium.go b/pkg/i2gw/providers/cilium/cilium.go new file mode 100644 index 00000000..aab4ca9c --- /dev/null +++ b/pkg/i2gw/providers/cilium/cilium.go @@ -0,0 +1,81 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import ( + "context" + "fmt" + + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// The Name of the provider. +const Name = "cilium" +const CiliumIngressClass = "cilium" + +func init() { + i2gw.ProviderConstructorByName[Name] = NewProvider +} + +// Provider implements the i2gw.Provider interface. +type Provider struct { + storage *storage + resourceReader *resourceReader + resourcesToIRConverter *resourcesToIRConverter +} + +// NewProvider constructs and returns the cilium implementation of i2gw.Provider. +func NewProvider(conf *i2gw.ProviderConf) i2gw.Provider { + return &Provider{ + storage: newResourcesStorage(), + resourceReader: newResourceReader(conf), + resourcesToIRConverter: newResourcesToIRConverter(), + } +} + +// ToIR converts stored Cilium API entities to intermediate.IR +// including the cilium specific features. +func (p *Provider) ToIR() (intermediate.IR, field.ErrorList) { + return p.resourcesToIRConverter.convertToIR(p.storage) +} + +func (p *Provider) ToGatewayResources(ir intermediate.IR) (i2gw.GatewayResources, field.ErrorList) { + return common.ToGatewayResources(ir) +} + +func (p *Provider) ReadResourcesFromCluster(ctx context.Context) error { + storage, err := p.resourceReader.readResourcesFromCluster(ctx) + if err != nil { + return fmt.Errorf("failed to read resources from cluster: %w", err) + } + + p.storage = storage + return nil +} + +func (p *Provider) ReadResourcesFromFile(_ context.Context, filename string) error { + storage, err := p.resourceReader.readResourcesFromFile(filename) + if err != nil { + return fmt.Errorf("failed to read resources from file: %w", err) + } + + p.storage = storage + return nil +} diff --git a/pkg/i2gw/providers/cilium/converter.go b/pkg/i2gw/providers/cilium/converter.go new file mode 100644 index 00000000..b317cdb7 --- /dev/null +++ b/pkg/i2gw/providers/cilium/converter.go @@ -0,0 +1,65 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import ( + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// resourcesToIRConverter implements the ToIR function of i2gw.ResourcesToIRConverter interface. +type resourcesToIRConverter struct { + featureParsers []i2gw.FeatureParser + implementationSpecificOptions i2gw.ProviderImplementationSpecificOptions +} + +// newResourcesToIRConverter returns a cilium resourcesToIRConverter instance. +func newResourcesToIRConverter() *resourcesToIRConverter { + return &resourcesToIRConverter{ + featureParsers: []i2gw.FeatureParser{ + forceHTTPSFeature, + }, + implementationSpecificOptions: i2gw.ProviderImplementationSpecificOptions{ + // The list of the implementationSpecific ingress fields options comes here. + }, + } +} + +func (c *resourcesToIRConverter) convertToIR(storage *storage) (intermediate.IR, field.ErrorList) { + ingressList := []networkingv1.Ingress{} + for _, ing := range storage.Ingresses { + ingressList = append(ingressList, *ing) + } + // Convert plain ingress resources to gateway resources, ignoring all + // provider-specific features. + ir, errs := common.ToIR(ingressList, c.implementationSpecificOptions) + if len(errs) > 0 { + return intermediate.IR{}, errs + } + + for _, parseFeatureFunc := range c.featureParsers { + // Apply the feature parsing function to the gateway resources, one by one. + parseErrs := parseFeatureFunc(ingressList, &ir) + // Append the parsing errors to the error list. + errs = append(errs, parseErrs...) + } + + return ir, errs +} diff --git a/pkg/i2gw/providers/cilium/force_https.go b/pkg/i2gw/providers/cilium/force_https.go new file mode 100644 index 00000000..b858837d --- /dev/null +++ b/pkg/i2gw/providers/cilium/force_https.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import ( + "fmt" + + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/notifications" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +func forceHTTPSFeature(ingresses []networkingv1.Ingress, ir *intermediate.IR) field.ErrorList { + var errs field.ErrorList + forceHTTPSAnnotation := ciliumAnnotation("force-https") + ruleGroups := common.GetRuleGroups(ingresses) + for _, rg := range ruleGroups { + + for _, rule := range rg.Rules { + if val, annotationFound := rule.Ingress.Annotations[forceHTTPSAnnotation]; val == "enabled" || val == "true" { + if rule.Ingress.Spec.Rules == nil { + continue + } + key := types.NamespacedName{Namespace: rule.Ingress.Namespace, Name: common.RouteName(rg.Name, rg.Host)} + + httpRoute, ok := ir.HTTPRoutes[key] + if !ok { + errs = append(errs, field.NotFound(field.NewPath("HTTPRoute"), key)) + } + + for i, rule := range httpRoute.Spec.Rules { + rule.Filters = append(rule.Filters, gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{ + Scheme: ptr.To("https"), + StatusCode: ptr.To(int(301)), + }, + }) + rule.BackendRefs = nil + + httpRoute.Spec.Rules[i] = rule + + } + if annotationFound && ok { + notify(notifications.InfoNotification, fmt.Sprintf("parsed \"%v\" annotation of ingress and patched %v fields", forceHTTPSAnnotation, field.NewPath("httproute", "spec", "rules").Key("").Child("filters")), &httpRoute) + } + } + } + } + return errs +} diff --git a/pkg/i2gw/providers/cilium/force_https_test.go b/pkg/i2gw/providers/cilium/force_https_test.go new file mode 100644 index 00000000..1f9fec04 --- /dev/null +++ b/pkg/i2gw/providers/cilium/force_https_test.go @@ -0,0 +1,341 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +func Test_forceHTTPSFeature(t *testing.T) { + testCases := []struct { + name string + ingress networkingv1.Ingress + initialHTTPRoute *gatewayv1.HTTPRoute + expectedHTTPRoute *gatewayv1.HTTPRoute + expectedError field.ErrorList + }{ + { + name: "force-https annontation present and set to enabled", + ingress: networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + Annotations: map[string]string{ + "ingress.cilium.io/force-https": "enabled", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "foo.com", + }, + }, + }, + }, + initialHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + expectedHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Filters: []gatewayv1.HTTPRouteFilter{ + { + Type: gatewayv1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{ + Scheme: ptr.To("https"), + StatusCode: ptr.To(int(301)), + }, + }, + }, + }, + }, + }, + }, + expectedError: field.ErrorList{}, + }, + { + name: "force-https annontation present and set to disabled", + ingress: networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + Annotations: map[string]string{ + "ingress.cilium.io/force-https": "disabled", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "foo.com", + }, + }, + }, + }, + initialHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + expectedHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + }, + { + name: "force-https annontation present and set to true", + ingress: networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + Annotations: map[string]string{ + "ingress.cilium.io/force-https": "true", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "foo.com", + }, + }, + }, + }, + initialHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + expectedHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Filters: []gatewayv1.HTTPRouteFilter{ + { + Type: gatewayv1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{ + Scheme: ptr.To("https"), + StatusCode: ptr.To(int(301)), + }, + }, + }, + }, + }, + }, + }, + expectedError: field.ErrorList{}, + }, + { + name: "force-https annontation present and set to false", + ingress: networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + Annotations: map[string]string{ + "ingress.cilium.io/force-https": "false", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "foo.com", + }, + }, + }, + }, + initialHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + expectedHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + expectedError: field.ErrorList{}, + }, + { + name: "force-https annotation not present", + ingress: networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "foo.com", + }, + }, + }, + }, + initialHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + expectedHTTPRoute: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress-foo-com", + Namespace: "default", + }, + Spec: gatewayv1.HTTPRouteSpec{ + Hostnames: []gatewayv1.Hostname{"foo.com"}, + Rules: []gatewayv1.HTTPRouteRule{ + { + BackendRefs: []gatewayv1.HTTPBackendRef{ + {BackendRef: gatewayv1.BackendRef{BackendObjectReference: gatewayv1.BackendObjectReference{Name: "foo", Port: ptr.To(gatewayv1.PortNumber(3000))}}}, + }, + }, + }, + }, + }, + expectedError: field.ErrorList{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ingresses := []networkingv1.Ingress{tc.ingress} + ir := &intermediate.IR{ + HTTPRoutes: map[types.NamespacedName]intermediate.HTTPRouteContext{ + {Name: tc.expectedHTTPRoute.Name, Namespace: tc.expectedHTTPRoute.Namespace}: { + HTTPRoute: *tc.initialHTTPRoute, + }, + }, + } + + errs := forceHTTPSFeature(ingresses, ir) + + if len(errs) != len(tc.expectedError) { + t.Errorf("expected %d errors, got %d", len(tc.expectedError), len(errs)) + } + + key := types.NamespacedName{Namespace: tc.ingress.Namespace, Name: common.RouteName(tc.ingress.Name, tc.ingress.Spec.Rules[0].Host)} + + actualHTTPRouteContext, ok := ir.HTTPRoutes[key] + if !ok { + t.Errorf("HTTPRoute not found: %v", key) + } + + if diff := cmp.Diff(*tc.expectedHTTPRoute, actualHTTPRouteContext.HTTPRoute); diff != "" { + t.Errorf("Unexpected HTTPRoute resource found, \n want: %+v\n got: %+v\n diff (-want +got):\n%s", *tc.expectedHTTPRoute, actualHTTPRouteContext.HTTPRoute, diff) + } + }) + } +} diff --git a/pkg/i2gw/providers/cilium/notification.go b/pkg/i2gw/providers/cilium/notification.go new file mode 100644 index 00000000..ed1f877f --- /dev/null +++ b/pkg/i2gw/providers/cilium/notification.go @@ -0,0 +1,27 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import ( + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/notifications" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func notify(mType notifications.MessageType, message string, callingObject ...client.Object) { + newNotification := notifications.NewNotification(mType, message, callingObject...) + notifications.NotificationAggr.DispatchNotification(newNotification, string(Name)) +} diff --git a/pkg/i2gw/providers/cilium/resource_reader.go b/pkg/i2gw/providers/cilium/resource_reader.go new file mode 100644 index 00000000..8bf74976 --- /dev/null +++ b/pkg/i2gw/providers/cilium/resource_reader.go @@ -0,0 +1,61 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import ( + "context" + + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" + "k8s.io/apimachinery/pkg/util/sets" +) + +// resourceReader implements the i2gw.CustomResourceReader interface. +type resourceReader struct { + conf *i2gw.ProviderConf +} + +// newResourceReader returns a resourceReader instance. +func newResourceReader(conf *i2gw.ProviderConf) *resourceReader { + return &resourceReader{ + conf: conf, + } +} + +func (r *resourceReader) readResourcesFromCluster(ctx context.Context) (*storage, error) { + // read cilium related resources from cluster. + storage := newResourcesStorage() + + ingresses, err := common.ReadIngressesFromCluster(ctx, r.conf.Client, sets.New(CiliumIngressClass)) + if err != nil { + return nil, err + } + storage.Ingresses = ingresses + return storage, nil +} + +func (r *resourceReader) readResourcesFromFile(filename string) (*storage, error) { + // read cilium related resources from file. + storage := newResourcesStorage() + + ingresses, err := common.ReadIngressesFromFile(filename, r.conf.Namespace, sets.New[string](CiliumIngressClass)) + if err != nil { + return nil, err + } + storage.Ingresses = ingresses + return storage, nil +} diff --git a/pkg/i2gw/providers/cilium/storage.go b/pkg/i2gw/providers/cilium/storage.go new file mode 100644 index 00000000..ae225d48 --- /dev/null +++ b/pkg/i2gw/providers/cilium/storage.go @@ -0,0 +1,32 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cilium + +import ( + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/types" +) + +type storage struct { + Ingresses map[types.NamespacedName]*networkingv1.Ingress +} + +func newResourcesStorage() *storage { + return &storage{ + Ingresses: map[types.NamespacedName]*networkingv1.Ingress{}, + } +}